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-negotiate-muxers-v2.patch

File sound-negotiate-muxers-v2.patch, 20.1 KB (added by Antoine Martin, 6 years ago)

newer work in progress patch

  • 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, engs, CLIENT_EXIT
     53from xpra.util import nonl, std, AtomicInteger, AdHocStruct, log_screen_sizes, typedict, updict, csv, CLIENT_EXIT
    5454from xpra.version_util import get_version_info_full, get_platform_info
    5555try:
    5656    from xpra.clipboard.clipboard_base import ALL_CLIPBOARDS
     
    153153        self.sound_out_bytecount = 0
    154154        self.server_pulseaudio_id = None
    155155        self.server_pulseaudio_server = None
    156         self.server_sound_decoders = []
    157         self.server_sound_encoders = []
     156        self.server_sound_muxers = []
     157        self.server_sound_demuxers = []
     158        self.server_sound_decoders = {}
     159        self.server_sound_encoders = {}
    158160        self.server_sound_receive = False
    159161        self.server_sound_send = False
    160162        self.queue_used_sent = None
     
    14141416        #sound:
    14151417        self.server_pulseaudio_id = c.strget("sound.pulseaudio.id")
    14161418        self.server_pulseaudio_server = c.strget("sound.pulseaudio.server")
    1417         self.server_sound_decoders = c.strlistget("sound.decoders", [])
    1418         self.server_sound_encoders = c.strlistget("sound.encoders", [])
     1419        self.server_sound_muxers = c.strlistget("sound.muxers", [])
     1420        self.server_sound_demuxers = c.strlistget("sound.muxers", [])
     1421        for m in self.server_sound_muxers:
     1422            self.server_sound_encoders[m] = c.strlistget("sound.encoders.%s" % m, [])
     1423        self.server_sound_encoders[""] = c.strlistget("sound.encoders", [])     #older servers - all encodings
     1424        for m in self.server_sound_demuxers:
     1425            self.server_sound_decoders[m] = c.strlistget("sound.decoders.%s" % m, [])
     1426        self.server_sound_decoders[""] = c.strlistget("sound.decoders", [])     #older servers - all encodings
    14191427        self.server_sound_receive = c.boolget("sound.receive")
    14201428        self.server_sound_send = c.boolget("sound.send")
    14211429        soundlog("pulseaudio id=%s, server=%s, sound decoders=%s, sound encoders=%s, receive=%s, send=%s",
     
    16061614            return False
    16071615        try:
    16081616            from xpra.sound.wrapper import start_sending_sound
    1609             ss = start_sending_sound(self.sound_source_plugin, None, 1.0, ordered_codecs, self.server_pulseaudio_server, self.server_pulseaudio_id)
     1617            ss = start_sending_sound(self.sound_source_plugin, ordered_codecs[0], 1.0, ordered_codecs, self.server_pulseaudio_server, self.server_pulseaudio_id)
    16101618            if not ss:
    16111619                return False
    16121620            self.sound_source = ss
  • xpra/server/source.py

     
    352352        #sound props:
    353353        self.pulseaudio_id = None
    354354        self.pulseaudio_server = None
    355         self.sound_decoders = []
    356         self.sound_encoders = []
     355        self.sound_muxers = []
     356        self.sound_demuxers = []
     357        self.sound_decoders = {}
     358        self.sound_encoders = {}
    357359        self.server_driven = False
    358360
    359361        self.keyboard_config = None
     
    634636        #sound stuff:
    635637        self.pulseaudio_id = c.strget("sound.pulseaudio.id")
    636638        self.pulseaudio_server = c.strget("sound.pulseaudio.server")
    637         self.sound_decoders = c.strlistget("sound.decoders", [])
    638         self.sound_encoders = c.strlistget("sound.encoders", [])
     639        self.sound_muxers = c.strlistget("sound.muxers", [])
     640        self.sound_demuxers = c.strlistget("sound.demuxers", [])
     641        for m in self.sound_muxers:
     642            self.sound_decoders[m] = c.strlistget("sound.decoders.%s" % m, [])
     643        self.sound_decoders[""] = c.strlistget("sound.decoders", [])    #older clients - all decoders
     644        for m in self.sound_demuxers:
     645            self.sound_encoders[m] = c.strlistget("sound.encoders.%s" % m, [])
     646        self.sound_encoders[""] = c.strlistget("sound.encoders", [])    #older clients - all encoders
    639647        self.sound_receive = c.boolget("sound.receive")
    640648        self.sound_send = c.boolget("sound.send")
    641649        self.server_driven = c.boolget("sound.server_driven")
     
    816824            assert self.supports_speaker, "cannot send sound: support not enabled on the server"
    817825            assert self.sound_source is None, "a sound source already exists"
    818826            assert self.sound_receive, "cannot send sound: support is not enabled on the client"
    819             assert codec in self.sound_decoders, "cannot use codec '%s' which is not in the decoders list: %s" % (codec, self.sound_decoders)
     827            #assert codec in self.sound_decoders, "cannot use codec '%s' which is not in the decoders list: %s" % (codec, self.sound_decoders)
    820828            from xpra.sound.wrapper import start_sending_sound
    821829            plugins = self.sound_properties.get("plugins")
     830            #TODO: choose muxer here?
    822831            ss = start_sending_sound(plugins, self.sound_source_plugin, codec, volume, [codec], self.pulseaudio_server, self.pulseaudio_id)
    823832            self.sound_source = ss
    824833            soundlog("start_sending_sound() sound source=%s", ss)
  • xpra/sound/gstreamer_util.py

     
    77import sys
    88import os
    99
     10from xpra.util import engs, csv
    1011from xpra.log import Logger
    1112log = Logger("sound")
    1213
     
    2829
    2930
    3031ALLOW_SOUND_LOOP = os.environ.get("XPRA_ALLOW_SOUND_LOOP", "0")=="1"
    31 GSTREAMER1 = os.environ.get("XPRA_GSTREAMER1", "0")=="1"
     32GSTREAMER1 = os.environ.get("XPRA_GSTREAMER1", "1")=="1"
    3233MONITOR_DEVICE_NAME = os.environ.get("XPRA_MONITOR_DEVICE_NAME", "")
    3334def force_enabled(codec_name):
    3435    return os.environ.get("XPRA_SOUND_CODEC_ENABLE_%s" % codec_name.upper(), "0")=="1"
     
    8182CODEC_OPTIONS = [
    8283            (VORBIS      , "vorbisenc",     "gdppay",   "vorbisdec",    "gdpdepay"),
    8384            (FLAC        , "flacenc",       "oggmux",   "flacdec",      "oggdemux"),
    84 #            (FLAC        , "flacenc",       "gdppay",   "flacdec",      "gdpdepay"),
     85            (FLAC        , "flacenc",       "gdppay",   "flacdec",      "gdpdepay"),
    8586            (MP3         , "lamemp3enc",    None,       "mad",          "mp3parse"),
    8687            (MP3         , "lamemp3enc",    None,       "mad",          "mpegaudioparse"),
    8788            (WAV         , "wavenc",        None,       None,           "wavparse"),
    8889            (OPUS        , "opusenc",       "oggmux",   "opusdec",      "oggdemux"),
    89 #            (OPUS        , "opusenc",       "gdppay",   "opusdec",      "gdpdepay"),
     90            (OPUS        , "opusenc",       "gdppay",   "opusdec",      "gdpdepay"),
    9091            (SPEEX       , "speexenc",      "oggmux",   "speexdec",     "oggdemux"),
    91 #            (SPEEX       , "speexenc",      "gdppay",   "speexdec",     "gdpdepay"),
     92            (SPEEX       , "speexenc",      "gdppay",   "speexdec",     "gdpdepay"),
    9293            (WAVPACK     , "wavpackenc",    None,       "wavpackdec",   "wavpackparse"),
    9394            ]
    9495
    9596GDP = "gdp"
    9697OGG = "ogg"
    97 MUX_OPTIONS = [
    98                (GDP,    "gdppay",  "gdpdepay"),
    99                (OGG,    "oggmux",  "oggdemux"),
    100               ]
     98MUX_OPTIONS = {
     99               GDP  : ("gdppay", "gdpdepay"),
     100               OGG  : ("oggmux", "oggdemux"),
     101              }
    101102emux = [x for x in os.environ.get("XPRA_MUXER_OPTIONS", "").split(",") if len(x.strip())>0]
    102103if emux:
    103     mo = [v for v in MUX_OPTIONS if v[0] in emux]
     104    imo = [v for v in emux if v not in MUX_OPTIONS.keys()]
     105    if len(imo)>0:
     106        log.warn("Warning: invalid muxer options %s", csv(imo))
     107    mo = [v for v in emux if v in MUX_OPTIONS]
    104108    if mo:
    105         MUX_OPTIONS = mo
    106     else:
    107         log.warn("Warning: invalid muxer options %s", emux)
     109        MUX_OPTIONS = dict([(v,MUX_OPTIONS[v]) for v in mo])
    108110    del mo
    109111del emux
    110112
     
    232234               (import_gst1, 1, "1.x"),
    233235               ]
    234236        if GSTREAMER1:
    235             imports = imports.reverse()  #try gst1 first:
     237            imports.reverse()  #try gst1 first:
    236238    errs = {}
    237239    for import_function, MV, vinfo in imports:
    238240        try:
     
    300302    CODECS = {}
    301303    for elements in CODEC_OPTIONS:
    302304        encoding = elements[0]
    303         if encoding in CODECS:
    304             #we already have one for this encoding
    305             continue
    306305        if force_enabled(encoding):
    307306            log.info("sound codec %s force enabled", encoding)
    308307        elif encoding==FLAC:
     
    324323        if has_plugins(*elements[1:]):
    325324            #ie: FLAC, "flacenc", "oggmux", "flacdec", "oggdemux" = elements
    326325            encoding, encoder, muxer, decoder, demuxer = elements
    327             CODECS[encoding] = (encoder, muxer, decoder, demuxer)
     326            CODECS.setdefault(encoding, []).append((encoder, muxer, decoder, demuxer))
    328327    log("initialized CODECS:")
    329328    for k in [x for x in CODEC_ORDER if x in CODECS]:
    330         log("* %s : %s", k, CODECS[k])
     329        log("* %s", k)
     330        options = CODECS[k]
     331        for option in options:
     332            log(" + %s", option)
    331333    return CODECS
    332334
    333335def get_muxers():
    334336    muxers = []
    335     for name,muxer,_ in MUX_OPTIONS:
    336         if has_plugins(muxer):
     337    for name,mux_def in MUX_OPTIONS.items():
     338        if has_plugins(mux_def[0]):
    337339            muxers.append(name)
    338340    return muxers
    339341
    340342def get_demuxers():
    341343    demuxers = []
    342     for name,_,demuxer in MUX_OPTIONS:
    343         if has_plugins(demuxer):
     344    for name,mux_def in MUX_OPTIONS.items():
     345        if has_plugins(mux_def[1]):
    344346            demuxers.append(name)
    345347    return demuxers
    346348
    347349
    348 def get_encoder_formatter(name):
     350def get_encoder_formatter(name, muxer_type=None):
    349351    codecs = get_codecs()
    350352    assert name in codecs, "invalid codec: %s (should be one of: %s)" % (name, codecs.keys())
    351     encoder, formatter, _, _ = codecs.get(name)
    352     assert encoder is None or has_plugins(encoder), "encoder %s not found" % encoder
    353     assert formatter is None or has_plugins(formatter), "formatter %s not found" % formatter
    354     return encoder, formatter
     353    muxer = None
     354    mdef = MUX_OPTIONS.get(muxer_type)
     355    if mdef:
     356        muxer = mdef[0]
     357    for encoder, formatter, _, _ in codecs.get(name):
     358        assert encoder is None or has_plugins(encoder), "encoder %s not found" % encoder
     359        assert formatter is None or has_plugins(formatter), "formatter %s not found" % formatter
     360        if muxer is None or muxer==formatter:
     361            return encoder, formatter
     362    raise Exception("no encoder+muxer found for %s+%s", (name, muxer_type))
    355363
    356 def get_decoder_parser(name):
     364def get_decoder_parser(name, muxer_type=None):
    357365    codecs = get_codecs()
    358366    assert name in codecs, "invalid codec: %s (should be one of: %s)" % (name, codecs.keys())
    359     _, _, decoder, parser = codecs.get(name)
    360     assert decoder is None or has_plugins(decoder), "decoder %s not found" % decoder
    361     assert parser is None or has_plugins(parser), "parser %s not found" % parser
    362     return decoder, parser
     367    demuxer = None
     368    mdef = MUX_OPTIONS.get(muxer_type)
     369    if mdef:
     370        demuxer = mdef[1]
     371    for _, _, decoder, parser in codecs.get(name):
     372        assert decoder is None or has_plugins(decoder), "decoder %s not found" % decoder
     373        assert parser is None or has_plugins(parser), "parser %s not found" % parser
     374        if demuxer is None or demuxer==parser:
     375            return decoder, parser
     376    raise Exception("no encoder+muxer found for %s+%s", (name, muxer_type))
    363377
    364 def has_encoder(name):
     378
     379def has_encoder(name, muxer=None):
     380    #log.info("has_encoder(%s, %s)", name, muxer)
    365381    codecs = get_codecs()
    366382    if name not in codecs:
    367383        return False
    368     encoder, fmt, _, _ = codecs.get(name)
    369     return has_plugins(encoder, fmt)
     384    for encoder, fmt, _, _ in codecs.get(name):
     385        if muxer and muxer!=fmt:
     386            continue
     387        if has_plugins(encoder, fmt):
     388            return True
     389    return False
    370390
    371 def has_decoder(name):
     391def has_decoder(name, demuxer=None):
    372392    codecs = get_codecs()
    373393    if name not in codecs:
    374394        return False
    375     _, _, decoder, parser = codecs.get(name)
    376     return has_plugins(decoder, parser)
     395    for _, _, decoder, parser in codecs.get(name):
     396        if demuxer and demuxer!=parser:
     397            continue
     398        if has_plugins(decoder, parser):
     399            return True
     400    return False
    377401
    378 def has_codec(name):
    379     return has_encoder(name) and has_decoder(name)
    380402
    381 def can_encode():
    382     return [x for x in CODEC_ORDER if has_encoder(x)]
     403def can_encode(muxer_type=None):
     404    muxer = None
     405    mdef = MUX_OPTIONS.get(muxer_type)
     406    if mdef:
     407        muxer = mdef[0]
     408    return [x for x in CODEC_ORDER if has_encoder(x, muxer)]
    383409
    384 def can_decode():
    385     return [x for x in CODEC_ORDER if has_decoder(x)]
     410def can_decode(muxer_type=None):
     411    demuxer = None
     412    mdef = MUX_OPTIONS.get(muxer_type)
     413    if mdef:
     414        demuxer = mdef[1]
     415    return [x for x in CODEC_ORDER if has_decoder(x, demuxer)]
    386416
     417
    387418def plugin_str(plugin, options):
    388419    if plugin is None:
    389420        return None
     
    557588
    558589
    559590def sound_option_or_all(name, options, all_values):
    560     from xpra.util import engs, csv
    561591    if not options:
    562592        v = all_values        #not specified on command line: use default
    563593    else:
     
    593623        print("GStreamer version: %s" % ".".join([str(x) for x in gst_version]))
    594624        print("PyGStreamer version: %s" % ".".join([str(x) for x in pygst_version]))
    595625        print("")
    596         encs = [x for x in CODEC_ORDER if has_encoder(x)]
    597         decs = [x for x in CODEC_ORDER if has_decoder(x)]
    598         print("encoders supported: %s" % ", ".join(encs))
    599         print("decoders supported: %s" % ", ".join(decs))
     626        for muxer_type in MUX_OPTIONS.keys():
     627            print("* %s muxer:" % muxer_type)
     628            print(" encoders: %s" % csv(can_encode(muxer_type)))
     629            print(" decoders: %s" % csv(can_decode(muxer_type)))
     630        print("* all:")
     631        print(" encoders: %s" % ", ".join(can_encode()))
     632        print(" decoders: %s" % ", ".join(can_decode()))
    600633    finally:
    601634        clean()
    602635
  • xpra/sound/sink.py

     
    6565        "eos"       : one_arg_signal,
    6666        })
    6767
    68     def __init__(self, sink_type=None, sink_options={}, codecs=get_codecs(), codec_options={}, volume=1.0):
     68    def __init__(self, sink_type=None, sink_options={}, codecs=get_codecs(), muxer_type=None, codec_options={}, volume=1.0):
    6969        if not sink_type:
    7070            sink_type = DEFAULT_SINK
    7171        if sink_type not in SINKS:
     
    7575        if not matching:
    7676            raise InitExit(1, "no matching codecs between arguments '%s' and supported list '%s'" % (csv(codecs), csv(get_codecs().keys())))
    7777        codec = matching[0]
    78         decoder, parser = get_decoder_parser(codec)
     78        decoder, parser = get_decoder_parser(codec, muxer_type)
    7979        SoundPipeline.__init__(self, codec)
    8080        self.sink_type = sink_type
    8181        self.levels = deque(maxlen=100)
  • xpra/sound/src.py

     
    2929        "new-buffer"    : n_arg_signal(2),
    3030        })
    3131
    32     def __init__(self, src_type=None, src_options={}, codecs=get_codecs(), codec_options={}, volume=1.0):
     32    def __init__(self, src_type=None, src_options={}, codecs=get_codecs(), muxer_type=None, codec_options={}, volume=1.0):
    3333        if not src_type:
    3434            from xpra.sound.pulseaudio_util import get_pa_device_options
    3535            monitor_devices = get_pa_device_options(True, False)
  • xpra/sound/wrapper.py

     
    119119    elif mode=="_sound_query":
    120120        plugins = get_all_plugin_names()
    121121        sources = [x for x in get_source_plugins() if x in plugins]
    122         from xpra.sound.gstreamer_util import gst_version, pygst_version
     122        from xpra.sound.gstreamer_util import gst_version, pygst_version, MUX_OPTIONS
    123123        d = {"encoders"         : can_encode(),
    124124             "decoders"         : can_decode(),
    125125             "sources"          : sources,
     
    129129             "pygst.version"    : pygst_version,
    130130             "plugins"          : plugins,
    131131            }
     132        for muxer_type in MUX_OPTIONS.keys():
     133            d["encoders.%s" % muxer_type] = can_encode(muxer_type)
     134            d["decoders.%s" % muxer_type] = can_encode(muxer_type)
    132135        for k,v in d.items():
    133136            print("%s=%s" % (k, ",".join(str(x) for x in v)))
    134137        return 0
     
    143146    options = parse_element_options(args[3])
    144147    #codecs:
    145148    codecs = [x.strip() for x in args[4].split(",")]
     149    muxer_type = args[5]
    146150    #codec options:
    147     codec_options = parse_element_options(args[5])
     151    codec_options = parse_element_options(args[6])
    148152    #volume (optional):
    149153    try:
    150         volume = int(args[6])
     154        volume = int(args[7])
    151155    except:
    152156        volume = 1.0
    153157
     
    269273
    270274class source_subprocess_wrapper(sound_subprocess_wrapper):
    271275
    272     def __init__(self, plugin, options, codecs, volume, element_options):
     276    def __init__(self, plugin, options, codec, muxer_type, volume, element_options):
    273277        sound_subprocess_wrapper.__init__(self, "sound-source")
    274278        self.large_packets = ["new-buffer"]
    275         self.command = get_sound_command()+["_sound_record", "-", "-", plugin or "", "", ",".join(codecs), "", str(volume)]
     279        self.command = get_sound_command()+["_sound_record", "-", "-", plugin or "", "", codec, muxer_type or "", "", str(volume)]
    276280        _add_debug_args(self.command)
    277281
    278282    def __repr__(self):
     
    284288
    285289class sink_subprocess_wrapper(sound_subprocess_wrapper):
    286290
    287     def __init__(self, plugin, options, codec, volume, element_options):
     291    def __init__(self, plugin, options, codec, muxer_type, volume, element_options):
    288292        sound_subprocess_wrapper.__init__(self, "sound-sink")
    289293        self.large_packets = ["add_data"]
    290294        self.codec = codec
    291         self.command = get_sound_command()+["_sound_play", "-", "-", plugin or "", "", codec, "", str(volume)]
     295        self.command = get_sound_command()+["_sound_play", "-", "-", plugin or "", "", codec, muxer_type or "", "", str(volume)]
    292296        _add_debug_args(self.command)
    293297
    294298    def add_data(self, data, metadata):
     
    318322        log("parsed '%s':", sound_source_plugin)
    319323        log("plugin=%s", plugin)
    320324        log("options=%s", options)
    321         return source_subprocess_wrapper(plugin, options, remote_decoders, volume, {})
     325        muxer_type = "ogg"
     326        return source_subprocess_wrapper(plugin, options, codec, muxer_type, volume, {})
    322327    except Exception as e:
    323328        log.error("error setting up sound: %s", e, exc_info=True)
    324329        return None
    325330
    326331
    327 def start_receiving_sound(codec):
    328     log("start_receiving_sound(%s)", codec)
     332def start_receiving_sound(codec, muxer_type):
     333    log("start_receiving_sound(%s, %s)", codec, muxer_type)
    329334    try:
    330         return sink_subprocess_wrapper(None, {}, codec, {}, 1.0)
     335        return sink_subprocess_wrapper(None, {}, codec, muxer_type, {}, 1.0)
    331336    except:
    332337        log.error("failed to start sound sink", exc_info=True)
    333338        return None
     
    350355        kv = x.split("=", 1)
    351356        if len(kv)==2:
    352357            #ie: kv = ["decoders", "mp3,vorbis"]
    353             d[kv[0]] = kv[1].split(",")     #d["decoders"] = ["mp3", "vorbis"]
     358            #    kv = ["decoders.ogg", "vorbis,speex"]
     359            d[kv[0].trim()] = [v.trim() for v in kv[1].split(",")]     #d["decoders"] = ["mp3", "vorbis"]
    354360    return d