Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* convert to codec full-names at network edge only: when we parse remote data, or when we send
* re-add "opus+mka" for gstreamer >= 1.8
* add plain "opus"
* re-add plain "flac"
* add "mp3+mpeg4" for browser mediasource audio support

git-svn-id: https://xpra.org/svn/Xpra/trunk@14401 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Nov 10, 2016
1 parent 478a38d commit 44143f0
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 68 deletions.
18 changes: 11 additions & 7 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1899,13 +1899,17 @@ def process_ui_capabilities(self):
#sound:
self.server_pulseaudio_id = c.strget("sound.pulseaudio.id")
self.server_pulseaudio_server = c.strget("sound.pulseaudio.server")
self.server_codec_full_names = c.boolget("sound.codec-full-names")
try:
from xpra.sound.common import legacy_to_new
self.server_sound_decoders = legacy_to_new(c.strlistget("sound.decoders", []))
self.server_sound_encoders = legacy_to_new(c.strlistget("sound.encoders", []))
if not self.server_codec_full_names:
from xpra.sound.common import legacy_to_new as conv
else:
def conv(v):
return v
self.server_sound_decoders = conv(c.strlistget("sound.decoders", []))
self.server_sound_encoders = conv(c.strlistget("sound.encoders", []))
except:
soundlog("cannot parse server sound codecs", exc_info=True)
self.server_codec_full_names = c.boolget("codec-full-names")
soundlog("Error: cannot parse server sound codec data", exc_info=True)
self.server_sound_receive = c.boolget("sound.receive")
self.server_sound_send = c.boolget("sound.send")
self.server_sound_bundle_metadata = c.boolget("sound.bundle-metadata")
Expand Down Expand Up @@ -2491,7 +2495,6 @@ def sound_sink_exit(self, sound_sink, *args):

def start_sound_sink(self, codec):
soundlog("start_sound_sink(%s)", codec)
codec = NEW_CODEC_NAMES.get(codec, codec)
assert self.sound_sink is None, "sound sink already exists!"
try:
soundlog("starting %s sound sink", codec)
Expand Down Expand Up @@ -2544,7 +2547,8 @@ def send_sound_data(self, sound_source, data, metadata={}, packet_metadata=()):
def _process_sound_data(self, packet):
codec, data, metadata = packet[1:4]
codec = bytestostr(codec)
codec = NEW_CODEC_NAMES.get(codec, codec)
if not self.server_codec_full_names:
codec = NEW_CODEC_NAMES.get(codec, codec)
metadata = typedict(metadata)
if data:
self.sound_in_bytecount += len(data)
Expand Down
39 changes: 24 additions & 15 deletions src/xpra/server/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,20 +743,24 @@ def parse_batch_int(value, varname):
#sound stuff:
self.pulseaudio_id = c.strget("sound.pulseaudio.id")
self.pulseaudio_server = c.strget("sound.pulseaudio.server")
self.codec_full_names = c.boolget("sound.codec-full-names")
try:
from xpra.sound.common import legacy_to_new
self.sound_decoders = legacy_to_new(c.strlistget("sound.decoders", []))
self.sound_encoders = legacy_to_new(c.strlistget("sound.encoders", []))
if not self.codec_full_names:
from xpra.sound.common import legacy_to_new as conv
else:
def conv(v):
return v
self.sound_decoders = conv(c.strlistget("sound.decoders", []))
self.sound_encoders = conv(c.strlistget("sound.encoders", []))
except:
soundlog("cannot parse client sound codecs", exc_info=True)
soundlog("Error: cannot parse client sound codecs", exc_info=True)
self.sound_receive = c.boolget("sound.receive")
self.sound_send = c.boolget("sound.send")
self.sound_bundle_metadata = c.boolget("sound.bundle-metadata")
self.codec_full_names = c.boolget("codec-full-names")
av_sync = c.boolget("av-sync")
self.set_av_sync_delay(int(self.av_sync and av_sync) * c.intget("av-sync.delay.default", 150))
soundlog("pulseaudio id=%s, server=%s, sound decoders=%s, sound encoders=%s, receive=%s, send=%s",
self.pulseaudio_id, self.pulseaudio_server, self.sound_decoders, self.sound_encoders, self.sound_receive, self.sound_send)
soundlog("pulseaudio id=%s, server=%s, full-names=%s, sound decoders=%s, sound encoders=%s, receive=%s, send=%s",
self.pulseaudio_id, self.pulseaudio_server, self.codec_full_names, self.sound_decoders, self.sound_encoders, self.sound_receive, self.sound_send)
avsynclog("av-sync: server=%s, client=%s, total=%s", self.av_sync, av_sync, self.av_sync_delay_total)

log("cursors=%s (encodings=%s), bell=%s, notifications=%s", self.send_cursors, self.cursor_encodings, self.send_bell, self.send_notifications)
Expand Down Expand Up @@ -959,7 +963,8 @@ def start_sending_sound(self, codec=None, volume=1.0):
if not self.sound_receive:
soundlog.error("Error sending sound: support is not enabled on the client")
return None
codec = NEW_CODEC_NAMES.get(codec, codec)
if not self.codec_full_names:
codec = NEW_CODEC_NAMES.get(codec, codec)
if codec is None:
codecs = [x for x in self.sound_decoders if x in self.speaker_codecs]
if not codecs:
Expand Down Expand Up @@ -1034,7 +1039,7 @@ def send_eos(self, codec, sequence=0):


def new_stream(self, sound_source, codec):
soundlog("new_stream(%s)", codec)
soundlog("new_stream(%s, %s)", sound_source, codec)
if self.sound_source!=sound_source:
soundlog("dropping new-stream signal (current source=%s, signal source=%s)", self.sound_source, sound_source)
return
Expand Down Expand Up @@ -1526,15 +1531,19 @@ def battr(k, prop):
return info

def get_sound_info(self):
def sound_info(supported, prop):
def sound_info(supported, prop, codecs):
i = {"codecs" : codecs}
if not supported:
return {"state" : "disabled"}
i["state"] = "disabled"
return i
if prop is None:
return {"state" : "inactive"}
return prop.get_info()
i["state"] = "inactive"
return i
i.update(prop.get_info())
return i
info = {
"speaker" : sound_info(self.supports_speaker, self.sound_source),
"microphone" : sound_info(self.supports_microphone, self.sound_sink),
"speaker" : sound_info(self.supports_speaker, self.sound_source, self.sound_decoders),
"microphone" : sound_info(self.supports_microphone, self.sound_sink, self.sound_encoders),
}
for prop in ("pulseaudio_id", "pulseaudio_server", "codec_full_names"):
v = getattr(self, prop)
Expand Down
6 changes: 3 additions & 3 deletions src/xpra/sound/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@
OPUS_OGG = OPUS+"+"+OGG
SPEEX_OGG = SPEEX+"+"+OGG
VORBIS_OGG = VORBIS+"+"+OGG
#OPUS_WEBM = OPUS+"+"+WEBM
OPUS_MKA = OPUS+"+"+MKA
#OPUS_RTP = OPUS+"+"+RTP
VORBIS_MKA = VORBIS+"+"+MKA
AAC_MPEG4 = AAC+"+"+MPEG4
WAV_LZ4 = WAV+"+"+LZ4
WAV_LZO = WAV+"+"+LZO
MP3_MPEG4 = MP3+"+"+MPEG4

#when codecs were first added, there was no support for multiple muxers,
#but now there is...
Expand Down Expand Up @@ -70,7 +71,7 @@ def legacy_to_new(codecs):

#used for parsing codec names specified on the command line:
def sound_option_or_all(name, options, all_values):
all_values = legacy_to_new(all_values)
log("sound_option_or_all%s", (name, options, all_values))
if not options:
v = all_values #not specified on command line: use default
else:
Expand All @@ -80,7 +81,6 @@ def sound_option_or_all(name, options, all_values):
#options is a list, but it may have csv embedded:
for o in x.split(","):
o = o.strip()
o = NEW_CODEC_NAMES.get(o, o)
if o not in all_values:
invalid_options.append(o)
else:
Expand Down
100 changes: 57 additions & 43 deletions src/xpra/sound/gstreamer_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import sys
import os

from xpra.sound.common import FLAC_OGG, OPUS_OGG, SPEEX_OGG, VORBIS_OGG, VORBIS_MKA, \
from xpra.sound.common import FLAC_OGG, OPUS_OGG, OPUS_MKA, SPEEX_OGG, VORBIS_OGG, VORBIS_MKA, \
AAC_MPEG4, WAV_LZ4, WAV_LZO, \
VORBIS, FLAC, MP3, OPUS, SPEEX, WAV, WAVPACK, \
VORBIS, FLAC, MP3, MP3_MPEG4, OPUS, SPEEX, WAV, WAVPACK, \
MPEG4, MKA, OGG

from xpra.util import csv, engs, parse_simple_dict, envint, envbool
Expand Down Expand Up @@ -89,29 +89,28 @@ def force_enabled(codec_name):
#we keep multiple options here for the same encoding
#and will populate the ones that are actually available into the "CODECS" dict
CODEC_OPTIONS = [
(VORBIS_MKA , "vorbisenc", "webmmux", "vorbisdec", "matroskademux", None),
#fails silently - no idea why:
#(VORBIS_OGG , "vorbisenc", "oggmux", "vorbisparse ! vorbisdec", "oggdemux"),
#does not work - no idea why:
#(FLAC , "flacenc", "oggmux", "flacparse ! flacdec", "oggdemux"),
(VORBIS_MKA , "vorbisenc", "webmmux", "vorbisdec", "matroskademux"),
#those two fail silently - no idea why:
#(VORBIS_OGG , "vorbisenc", "oggmux", "vorbisparse ! vorbisdec", "oggdemux"),
#(VORBIS , "vorbisenc", None, "vorbisparse ! vorbisdec", None),
(FLAC , "flacenc", None, "flacparse ! flacdec", None),
#this only works in gstreamer 0.10 and is filtered out during initialization:
(FLAC_OGG , "flacenc", "oggmux", "flacdec", "oggdemux", None),
(MP3 , "lamemp3enc", None, "mp3parse ! mad", None, None),
(MP3 , "lamemp3enc", None, "mpegaudioparse ! mad", None, None),
(WAV , "wavenc", None, "wavparse", None, None),
(WAV_LZ4 , "wavenc", None, "wavparse", None, "lz4"),
(WAV_LZO , "wavenc", None, "wavparse", None, "lzo"),
(OPUS_OGG , "opusenc", "oggmux", "opusdec", "oggdemux", None),
#for rtp, we would need to send the caps:
#(OPUS_RTP , "opusenc", "rtpopuspay", "opusdec", "rtpopusdepay"),
#(OPUS_RTP , "opusenc", "rtpopuspay", "opusparse ! opusdec", "rtpopusdepay"),
#this causes "could not link opusenc0 to webmmux0"
#(OPUS_WEBM , "opusenc", "webmmux", "opusdec", "matroskademux"),
#(OPUS_WEBM , "opusenc", "webmmux", "opusparse ! opusdec", "matroskademux"),
(SPEEX_OGG , "speexenc", "oggmux", "speexdec", "oggdemux", None),
(WAVPACK , "wavpackenc", None, "wavpackparse ! wavpackdec", None, None),
(AAC_MPEG4 , "faac", "mp4mux", "faad", "qtdemux", None),
(AAC_MPEG4 , "avenc_aac", "mp4mux", "avdec_aac", "qtdemux", None),
(FLAC_OGG , "flacenc", "oggmux", "flacparse ! flacdec", "oggdemux"),
(MP3 , "lamemp3enc", None, "mp3parse ! mad", None),
(MP3 , "lamemp3enc", None, "mpegaudioparse ! mad", None),
(MP3_MPEG4 , "lamemp3enc", "mp4mux", "mp3parse ! mad", "qtdemux"),
(MP3_MPEG4 , "lamemp3enc", "mp4mux", "mpegaudioparse ! mad", "qtdemux"),
(WAV , "wavenc", None, "wavparse", None),
(WAV_LZ4 , "wavenc", None, "wavparse", None, "lz4"),
(WAV_LZO , "wavenc", None, "wavparse", None, "lzo"),
(OPUS_OGG , "opusenc", "oggmux", "opusdec", "oggdemux"),
(OPUS , "opusenc", None, "opusparse ! opusdec", None),
#this can cause "could not link opusenc0 to webmmux0"
(OPUS_MKA , "opusenc", "webmmux", "opusdec", "matroskademux"),
(SPEEX_OGG , "speexenc", "oggmux", "speexdec", "oggdemux"),
(WAVPACK , "wavpackenc", None, "wavpackparse ! wavpackdec", None),
(AAC_MPEG4 , "faac", "mp4mux", "faad", "qtdemux"),
(AAC_MPEG4 , "avenc_aac", "mp4mux", "avdec_aac", "qtdemux"),
]

MUX_OPTIONS = [
Expand All @@ -138,13 +137,21 @@ def force_enabled(codec_name):
#options we use to tune for low latency:
OGG_DELAY = 20*MS_TO_NS
ENCODER_DEFAULT_OPTIONS_COMMON = {
"lamemp3enc" : {"encoding-engine-quality": 0}, #"fast"
"lamemp3enc" : {
"encoding-engine-quality" : 0,
}, #"fast"
"wavpackenc" : {
"mode" : 1, #"fast" (0 aka "very fast" is not supported)
"mode" : 1, #"fast" (0 aka "very fast" is not supported)
"bitrate" : 256000,
},
"flacenc" : {"quality" : 0}, #"fast"
"avenc_aac" : {"compliance" : -2} #allows experimental
"flacenc" : {
"quality" : 0, #"fast"
},
"avenc_aac" : {
"compliance" : -2, #allows experimental
},
#"faac" : {"perfect-timestamp" : 1},
#"vorbisenc" : {"perfect-timestamp" : 1},
}
ENCODER_DEFAULT_OPTIONS = {
0 : {
Expand All @@ -164,13 +171,18 @@ def force_enabled(codec_name):
}
#we may want to review this if/when we implement UDP transport:
MUXER_DEFAULT_OPTIONS = {
"oggmux" : {"max-delay" : OGG_DELAY,
"max-page-delay" : OGG_DELAY,
"oggmux" : {
"max-delay" : OGG_DELAY,
"max-page-delay" : OGG_DELAY,
},
"webmmux" : {
"writing-app" : "Xpra",
"streamable" : 1,
"min-index-interval" : 100000000,
},
"webmmux" : {"writing-app" : "Xpra"},
"mp4mux" : {
"faststart" : 1,
"streamable" : 1,
"faststart" : 1,
"streamable" : 1,
"fragment-duration" : 1,
"presentation-time" : 0,
}
Expand All @@ -189,7 +201,7 @@ def force_enabled(codec_name):
SPEEX : 0,
}

CODEC_ORDER = [OPUS_OGG, VORBIS_MKA, FLAC_OGG, MP3, AAC_MPEG4, WAV_LZ4, WAV_LZO, WAV, WAVPACK, SPEEX_OGG]
CODEC_ORDER = [OPUS_OGG, VORBIS_MKA, FLAC_OGG, MP3, AAC_MPEG4, WAV_LZ4, WAV_LZO, WAV, WAVPACK, SPEEX_OGG, OPUS, VORBIS, FLAC_OGG, OPUS_MKA, FLAC, MP3_MPEG4]


gst = None
Expand Down Expand Up @@ -430,7 +442,7 @@ def init_codecs():
if not validate_encoding(elements):
continue
try:
encoding, encoder, payloader, decoder, depayloader, stream_compressor = elements
encoding, encoder, payloader, decoder, depayloader, stream_compressor = (list(elements)+[None])[:6]
except ValueError as e:
log.error("Error: invalid codec entry: %s", e)
log.error(" %s", elements)
Expand Down Expand Up @@ -475,7 +487,7 @@ def validate_encoding(elements):
if force_enabled(encoding):
log.info("sound codec %s force enabled", encoding)
return True
elif len([x for x in elements if (x and (x.find("matroska")>=0 or x.find("gdp")>=0))])>0 and get_gst_version()<(1, ):
elif len([x for x in elements if x and (x.find("matroska")>=0)])>0 and get_gst_version()<(1, ):
#outdated versions of gstreamer cause problems with the gdp and matroskademux muxers,
#the receiver may not be able to process the data
#and we have no way of knowing what version they have at this point, so just disable those:
Expand All @@ -489,20 +501,22 @@ def validate_encoding(elements):
#so avoid using those:
log("avoiding outdated flac module (likely buggy on win32 with gstreamer 0.10)")
return False
elif encoding==FLAC and gst_major_version==1:
log("skipping flac with GStreamer 1.x to avoid obscure 'not-negotiated' errors")
return False
elif encoding==FLAC_OGG:
log("skipping %s to avoid obscure 'not-negotiated' errors", encoding)
return False
elif WIN32 and encoding in (SPEEX_OGG, ):
log("skipping %s on win32", encoding)
return False
elif encoding.startswith(OPUS):
if gst_major_version<1:
log("skipping %s with GStreamer 0.10", encoding)
return False
stream_compressor = elements[5]
if encoding==OPUS_MKA and get_gst_version()<(1, 8):
#this causes "could not link opusenc0 to webmmux0"
#(not sure which versions are affected, but 1.8.x is not)
log("skipping %s with GStreamer %s", encoding, get_gst_version())
return False
try:
stream_compressor = elements[5]
except:
stream_compressor = None
if stream_compressor and not has_stream_compressor(stream_compressor):
log("skipping %s: missing %s", encoding, stream_compressor)
return False
Expand Down

0 comments on commit 44143f0

Please sign in to comment.