From 72b6fd295fa1f89a1dac472d53f1e2de84dfc2dc Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 11:36:21 +0100 Subject: [PATCH 01/25] factoring tx steps out into functions (definitely doesn't work yet) --- openob/rtp/tx.py | 233 ++++++++++++++++++++++++++--------------------- 1 file changed, 129 insertions(+), 104 deletions(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index d660ca4..bdf3c57 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -11,85 +11,83 @@ class RTPTransmitter(object): def __init__(self, node_name, link_config, audio_interface): """Sets up a new RTP transmitter""" + self.logger_factory = LoggerFactory() + self.logger = self.logger_factory.getLogger('node.%s.link.%s.%s' % (node_name, self.link_config.name, self.audio_interface.mode)) + self.logger.info("Creating RTP transmission pipeline") + self.started = False self.caps = 'None' + + self.build_pipeline(link_config, audio_interface) + + def run(self): + self.pipeline.set_state(gst.STATE_PLAYING) + while self.caps == 'None': + self.logger.debug(udpsink_rtpout.get_state()) + self.caps = str( + udpsink_rtpout.get_pad('sink').get_property('caps')) + # Fix for gstreamer bug in rtpopuspay fixed in GST-plugins-bad + # 50140388d2b62d32dd9d0c071e3051ebc5b4083b, bug 686547 + if self.link_config.encoding == 'opus': + self.caps = re.sub(r'(caps=.+ )', '', self.caps) + + if self.caps == 'None': + self.logger.warn("Waiting for audio interface/caps") + + time.sleep(0.1) + + def loop(self): + try: + self.loop = gobject.MainLoop() + self.loop.run() + except Exception as e: + self.logger.exception("Encountered a problem in the MainLoop, tearing down the pipeline: %s" % e) + self.pipeline.set_state(gst.STATE_NULL) + + def build_pipeline(self, link_config, audio_interface): self.pipeline = gst.Pipeline("tx") - self.bus = self.pipeline.get_bus() - self.bus.connect("message", self.on_message) + bus = self.pipeline.get_bus() + bus.connect("message", self.on_message) + self.link_config = link_config self.audio_interface = audio_interface - self.logger_factory = LoggerFactory() - self.logger = self.logger_factory.getLogger('node.%s.link.%s.%s' % (node_name, self.link_config.name, self.audio_interface.mode)) - self.logger.info("Creating RTP transmission pipeline") + + source = self.build_audio_interface() + encoder = self.build_encoder() + transport = self.build_transport() + + self.pipeline.add(source, encoder, transport) + gst.element_link_many(source, encoder, transport) + + # Connect our bus up + bus.add_signal_watch() + bus.connect('message', self.on_message) + + def build_audio_interface(self): + bin = gst.Bin('audio') + # Audio input if self.audio_interface.type == 'auto': - self.source = gst.element_factory_make('autoaudiosrc') + source = gst.element_factory_make('autoaudiosrc') elif self.audio_interface.type == 'alsa': - self.source = gst.element_factory_make('alsasrc') - self.source.set_property('device', self.audio_interface.alsa_device) + source = gst.element_factory_make('alsasrc') + source.set_property('device', self.audio_interface.alsa_device) elif self.audio_interface.type == 'jack': - self.source = gst.element_factory_make("jackaudiosrc") + source = gst.element_factory_make("jackaudiosrc") if self.audio_interface.jack_auto: - self.source.set_property('connect', 'auto') + source.set_property('connect', 'auto') else: - self.source.set_property('connect', 'none') - self.source.set_property('buffer-time', 50000) - self.source.set_property('name', self.audio_interface.jack_name) - self.source.set_property('client-name', self.audio_interface.jack_name) + source.set_property('connect', 'none') + source.set_property('buffer-time', 50000) + source.set_property('name', self.audio_interface.jack_name) + source.set_property('client-name', self.audio_interface.jack_name) # Audio resampling and conversion - self.audioresample = gst.element_factory_make("audioresample") - self.audioconvert = gst.element_factory_make("audioconvert") - self.audioresample.set_property('quality', 9) # SRC - - # Encoding and payloading - if self.link_config.encoding == 'opus': - self.encoder = gst.element_factory_make("opusenc", "encoder") - self.encoder.set_property('bitrate', int(self.link_config.bitrate) * 1000) - self.encoder.set_property('tolerance', 80000000) - self.encoder.set_property('frame-size', self.link_config.opus_framesize) - self.encoder.set_property('complexity', int(self.link_config.opus_complexity)) - self.encoder.set_property('inband-fec', self.link_config.opus_fec) - self.encoder.set_property('packet-loss-percentage', int(self.link_config.opus_loss_expectation)) - self.encoder.set_property('dtx', self.link_config.opus_dtx) - print(self.encoder.get_properties('bitrate', 'dtx', 'inband-fec')) - self.payloader = gst.element_factory_make("rtpopuspay", "payloader") - elif self.link_config.encoding == 'pcm': - # we have no encoder for PCM operation - self.payloader = gst.element_factory_make("rtpL16pay", "payloader") - else: - self.logger.critical("Unknown encoding type %s" % self.link_config.encoding) - # TODO: Add a tee here, and sort out creating multiple UDP sinks for multipath - # Now the RTP bits - # We'll send audio out on this - self.udpsink_rtpout = gst.element_factory_make("udpsink", "udpsink_rtp") - self.udpsink_rtpout.set_property('host', self.link_config.receiver_host) - self.udpsink_rtpout.set_property('port', self.link_config.port) - self.logger.info('Set receiver to %s:%i' % (self.link_config.receiver_host, self.link_config.port)) - - if self.link_config.multicast: - self.udpsink_rtpout.set_property('auto_multicast', True) - self.logger.info('Multicast mode enabled') - - # Our RTP manager - self.rtpbin = gst.element_factory_make("gstrtpbin", "gstrtpbin") - self.rtpbin.set_property('latency', 0) - # Our level monitor - self.level = gst.element_factory_make("level") - self.level.set_property('message', True) - self.level.set_property('interval', 1000000000) + audioresample = gst.element_factory_make("audioresample") + audioconvert = gst.element_factory_make("audioconvert") + audioresample.set_property('quality', 9) # SRC # Add a capsfilter to allow specification of input sample rate - self.capsfilter = gst.element_factory_make("capsfilter") - - # Add to the pipeline - self.pipeline.add( - self.source, self.capsfilter, self.audioresample, self.audioconvert, - self.payloader, self.udpsink_rtpout, self.rtpbin, - self.level) - - if self.link_config.encoding != 'pcm': - # Only add the encoder if we're not in PCM mode - self.pipeline.add(self.encoder) + capsfilter = gst.element_factory_make("capsfilter") # Decide which format to apply to the capsfilter (Jack uses float) if self.audio_interface.type == 'jack': @@ -99,57 +97,84 @@ def __init__(self, node_name, link_config, audio_interface): # if audio_rate has been specified, then add that to the capsfilter if self.audio_interface.samplerate != 0: - self.capsfilter.set_property( + capsfilter.set_property( "caps", gst.Caps('%s, channels=2, rate=%d' % (data_type, self.audio_interface.samplerate))) else: - self.capsfilter.set_property( + capsfilter.set_property( "caps", gst.Caps('%s, channels=2' % data_type)) - # Then continue linking the pipeline together + # Our level monitor + level = gst.element_factory_make("level") + level.set_property('message', True) + level.set_property('interval', 1000000000) + + bin.add( + source, capsfilter, level, audioresample, audioconvert + ) + gst.element_link_many( - self.source, self.capsfilter, self.level, self.audioresample, self.audioconvert) + source, capsfilter, level, audioresample, audioconvert + ) - # Now we get to link this up to our encoder/payloader - if self.link_config.encoding != 'pcm': - gst.element_link_many( - self.audioconvert, self.encoder, self.payloader) + return bin + + def build_encoder(self): + bin = gst.Bin('encoder') + + # Encoding and payloading + if self.link_config.encoding == 'opus': + encoder = gst.element_factory_make("opusenc", "encoder") + encoder.set_property('bitrate', int(self.link_config.bitrate) * 1000) + encoder.set_property('tolerance', 80000000) + encoder.set_property('frame-size', self.link_config.opus_framesize) + encoder.set_property('complexity', int(self.link_config.opus_complexity)) + encoder.set_property('inband-fec', self.link_config.opus_fec) + encoder.set_property('packet-loss-percentage', int(self.link_config.opus_loss_expectation)) + encoder.set_property('dtx', self.link_config.opus_dtx) + print(encoder.get_properties('bitrate', 'dtx', 'inband-fec')) + payloader = gst.element_factory_make("rtpopuspay", "payloader") + elif self.link_config.encoding == 'pcm': + # we have no encoder for PCM operation + payloader = gst.element_factory_make("rtpL16pay", "payloader") else: - gst.element_link_many(self.audioconvert, self.payloader) + self.logger.critical("Unknown encoding type %s" % self.link_config.encoding) - # And now the RTP bits - self.payloader.link_pads('src', self.rtpbin, 'send_rtp_sink_0') - self.rtpbin.link_pads('send_rtp_src_0', self.udpsink_rtpout, 'sink') - # self.udpsrc_rtcpin.link_pads('src', self.rtpbin, 'recv_rtcp_sink_0') - # # RTCP SRs - # self.rtpbin.link_pads('send_rtcp_src_0', self.udpsink_rtcpout, 'sink') - # Connect our bus up - self.bus.add_signal_watch() - self.bus.connect('message', self.on_message) + if self.link_config.encoding != 'pcm': + # Only add the encoder if we're not in PCM mode + bin.add(encoder) + gst.element_link_many(encoder, payloader) + + bin.add(payloader) - def run(self): - self.pipeline.set_state(gst.STATE_PLAYING) - while self.caps == 'None': - self.logger.debug(self.udpsink_rtpout.get_state()) - self.caps = str( - self.udpsink_rtpout.get_pad('sink').get_property('caps')) - # Fix for gstreamer bug in rtpopuspay fixed in GST-plugins-bad - # 50140388d2b62d32dd9d0c071e3051ebc5b4083b, bug 686547 - if self.link_config.encoding == 'opus': - self.caps = re.sub(r'(caps=.+ )', '', self.caps) + return bin - if self.caps == 'None': - self.logger.warn("Waiting for audio interface/caps") + def build_transport(self): + bin = gst.Bin('transport') - time.sleep(0.1) + # TODO: Add a tee here, and sort out creating multiple UDP sinks for multipath + # Now the RTP bits + # We'll send audio out on this + udpsink_rtpout = gst.element_factory_make("udpsink", "udpsink_rtp") + udpsink_rtpout.set_property('host', self.link_config.receiver_host) + udpsink_rtpout.set_property('port', self.link_config.port) + self.logger.info('Set receiver to %s:%i' % (self.link_config.receiver_host, self.link_config.port)) + if self.link_config.multicast: + udpsink_rtpout.set_property('auto_multicast', True) + self.logger.info('Multicast mode enabled') - def loop(self): - try: - self.loop = gobject.MainLoop() - self.loop.run() - except Exception as e: - self.logger.exception("Encountered a problem in the MainLoop, tearing down the pipeline: %s" % e) - self.pipeline.set_state(gst.STATE_NULL) + # Our RTP manager + rtpbin = gst.element_factory_make("gstrtpbin", "gstrtpbin") + rtpbin.set_property('latency', 0) + + # And now the RTP bits + payloader.link_pads('src', rtpbin, 'send_rtp_sink_0') + rtpbin.link_pads('send_rtp_src_0', udpsink_rtpout, 'sink') + # self.udpsrc_rtcpin.link_pads('src', rtpbin, 'recv_rtcp_sink_0') + # # RTCP SRs + # rtpbin.link_pads('send_rtcp_src_0', self.udpsink_rtcpout, 'sink') + + bin.add(udpsink_rtpout, rtpbin) def on_message(self, bus, message): if message.type == gst.MESSAGE_ELEMENT: @@ -157,7 +182,7 @@ def on_message(self, bus, message): if self.started is False: self.started = True #gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, gst.DEBUG_GRAPH_SHOW_ALL, 'tx-graph') - #self.logger.debug(self.source.get_property('actual-buffer-time')) + #self.logger.debug(source.get_property('actual-buffer-time')) if len(message.structure['peak']) == 1: self.logger.info("Started mono audio transmission") else: From c48e4c729fa93a55375a6432d6d9eb882162f974 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 11:39:35 +0100 Subject: [PATCH 02/25] remove references to old gobject/gst --- openob/node.py | 2 -- openob/rtp/rx.py | 6 ++---- openob/rtp/tx.py | 6 ++---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/openob/node.py b/openob/node.py index d98d542..4418b00 100755 --- a/openob/node.py +++ b/openob/node.py @@ -4,8 +4,6 @@ from openob.rtp.tx import RTPTransmitter from openob.rtp.rx import RTPReceiver from openob.link_config import LinkConfig -from gst import ElementNotFoundError - class Node(object): diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 3eb703c..154485f 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -1,7 +1,5 @@ -import gobject -import pygst -pygst.require("0.10") -import gst + + import re from openob.logger import LoggerFactory diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index bdf3c57..3c095c1 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -1,7 +1,5 @@ -import gobject -import pygst -pygst.require("0.10") -import gst + + import time import re from openob.logger import LoggerFactory From e3641ea1f0fb786eb8c3a86bb0d4f63ed6f1dcd8 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 12:31:33 +0100 Subject: [PATCH 03/25] Added test audio source and removed Gst import in Node --- bin/openob | 2 +- openob/node.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/openob b/bin/openob index ac61a3a..81d1799 100755 --- a/bin/openob +++ b/bin/openob @@ -21,7 +21,7 @@ subparsers = parser.add_subparsers(help="The link mode to operate in on this end parser_tx = subparsers.add_parser('tx', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_tx.add_argument('receiver_host', type=str, help="The receiver for this transmitter. The machine at this address must be running an rx-mode Manager for this link name") -parser_tx.add_argument('-a', '--audio_input', type=str, choices=['auto', 'alsa', 'jack'], default='auto', help="The audio source type for this end of the link") +parser_tx.add_argument('-a', '--audio_input', type=str, choices=['auto', 'alsa', 'jack', 'test'], default='auto', help="The audio source type for this end of the link") parser_tx_alsa = parser_tx.add_argument_group('alsa', 'Options when using ALSA source type') parser_tx_alsa.add_argument('-d', '--alsa_device', type=str, default='hw:0', help="The ALSA device to connect to for input") parser_tx_jack = parser_tx.add_argument_group('jack', 'Options when using JACK source type') diff --git a/openob/node.py b/openob/node.py index 4418b00..1f158a2 100755 --- a/openob/node.py +++ b/openob/node.py @@ -48,9 +48,6 @@ def run_link(self, link_config, audio_interface): link_logger.debug("Got caps from transmitter, setting config") link_config.set("caps", caps) transmitter.loop() - except ElementNotFoundError as e: - link_logger.critical("GStreamer element missing: %s - will now exit" % e) - sys.exit(1) except Exception as e: link_logger.exception("Transmitter crashed for some reason! Restarting...") time.sleep(0.5) @@ -63,9 +60,6 @@ def run_link(self, link_config, audio_interface): receiver = RTPReceiver(self.node_name, link_config, audio_interface) receiver.run() receiver.loop() - except ElementNotFoundError as e: - link_logger.critical("GStreamer element missing: %s - will now exit" % e) - sys.exit(1) except Exception as e: link_logger.exception("Receiver crashed for some reason! Restarting...") time.sleep(0.1) From 23045855a2c75b0878da7e22b8e2f4ca623bbeff Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 12:31:44 +0100 Subject: [PATCH 04/25] Updated TX class to Gst 1.0 --- openob/rtp/tx.py | 169 ++++++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 77 deletions(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index 3c095c1..c5b2702 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -1,7 +1,11 @@ - +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst +Gst.init(None) import time import re +import sys from openob.logger import LoggerFactory @@ -9,28 +13,24 @@ class RTPTransmitter(object): def __init__(self, node_name, link_config, audio_interface): """Sets up a new RTP transmitter""" + + self.link_config = link_config + self.audio_interface = audio_interface + self.logger_factory = LoggerFactory() self.logger = self.logger_factory.getLogger('node.%s.link.%s.%s' % (node_name, self.link_config.name, self.audio_interface.mode)) - self.logger.info("Creating RTP transmission pipeline") + self.logger.info('Creating transmission pipeline') - self.started = False - self.caps = 'None' - - self.build_pipeline(link_config, audio_interface) + self.build_pipeline() def run(self): - self.pipeline.set_state(gst.STATE_PLAYING) + self.pipeline.set_state(Gst.State.PLAYING) while self.caps == 'None': - self.logger.debug(udpsink_rtpout.get_state()) self.caps = str( - udpsink_rtpout.get_pad('sink').get_property('caps')) - # Fix for gstreamer bug in rtpopuspay fixed in GST-plugins-bad - # 50140388d2b62d32dd9d0c071e3051ebc5b4083b, bug 686547 - if self.link_config.encoding == 'opus': - self.caps = re.sub(r'(caps=.+ )', '', self.caps) + self.transport.get_static_pad('sink').get_property('caps')) if self.caps == 'None': - self.logger.warn("Waiting for audio interface/caps") + self.logger.warn('Waiting for audio interface/caps') time.sleep(0.1) @@ -39,39 +39,42 @@ def loop(self): self.loop = gobject.MainLoop() self.loop.run() except Exception as e: - self.logger.exception("Encountered a problem in the MainLoop, tearing down the pipeline: %s" % e) - self.pipeline.set_state(gst.STATE_NULL) + self.logger.exception('Encountered a problem in the MainLoop, tearing down the pipeline: %s' % e) + self.pipeline.set_state(Gst.State.NULL) - def build_pipeline(self, link_config, audio_interface): - self.pipeline = gst.Pipeline("tx") - bus = self.pipeline.get_bus() - bus.connect("message", self.on_message) + def build_pipeline(self): + self.pipeline = Gst.Pipeline.new('tx') - self.link_config = link_config - self.audio_interface = audio_interface + self.started = False + self.caps = 'None' - source = self.build_audio_interface() - encoder = self.build_encoder() - transport = self.build_transport() + bus = self.pipeline.get_bus() + bus.connect('message', self.on_message) + + self.source = self.build_audio_interface() + self.encoder = self.build_encoder() + self.transport = self.build_transport() - self.pipeline.add(source, encoder, transport) - gst.element_link_many(source, encoder, transport) + self.pipeline.add(self.source, self.encoder, self.transport) + self.source.link(self.encoder) + self.encoder.link(self.transport) # Connect our bus up bus.add_signal_watch() bus.connect('message', self.on_message) def build_audio_interface(self): - bin = gst.Bin('audio') + self.logger.debug('Building audio input bin') + bin = Gst.Bin('audio') # Audio input if self.audio_interface.type == 'auto': - source = gst.element_factory_make('autoaudiosrc') + source = Gst.ElementFactory.make('autoaudiosrc') elif self.audio_interface.type == 'alsa': - source = gst.element_factory_make('alsasrc') + source = Gst.ElementFactory.make('alsasrc') source.set_property('device', self.audio_interface.alsa_device) elif self.audio_interface.type == 'jack': - source = gst.element_factory_make("jackaudiosrc") + source = Gst.ElementFactory.make('jackaudiosrc') if self.audio_interface.jack_auto: source.set_property('connect', 'auto') else: @@ -79,13 +82,21 @@ def build_audio_interface(self): source.set_property('buffer-time', 50000) source.set_property('name', self.audio_interface.jack_name) source.set_property('client-name', self.audio_interface.jack_name) + elif self.audio_interface.type == 'test': + source = Gst.ElementFactory.make('audiotestsrc') + + bin.add(source) + # Audio resampling and conversion - audioresample = gst.element_factory_make("audioresample") - audioconvert = gst.element_factory_make("audioconvert") - audioresample.set_property('quality', 9) # SRC + resample = Gst.ElementFactory.make('audioresample') + resample.set_property('quality', 9) # SRC + bin.add(resample) + + convert = Gst.ElementFactory.make('audioconvert') + bin.add(convert) # Add a capsfilter to allow specification of input sample rate - capsfilter = gst.element_factory_make("capsfilter") + capsfilter = Gst.ElementFactory.make('capsfilter') # Decide which format to apply to the capsfilter (Jack uses float) if self.audio_interface.type == 'jack': @@ -96,32 +107,34 @@ def build_audio_interface(self): # if audio_rate has been specified, then add that to the capsfilter if self.audio_interface.samplerate != 0: capsfilter.set_property( - "caps", gst.Caps('%s, channels=2, rate=%d' % (data_type, self.audio_interface.samplerate))) + 'caps', Gst.Caps('%s, channels=2, rate=%d' % (data_type, self.audio_interface.samplerate))) else: capsfilter.set_property( - "caps", gst.Caps('%s, channels=2' % data_type)) + 'caps', Gst.Caps('%s, channels=2' % data_type)) + bin.add(capsfilter) # Our level monitor - level = gst.element_factory_make("level") + level = Gst.ElementFactory.make('level') level.set_property('message', True) level.set_property('interval', 1000000000) + bin.add(level) - bin.add( - source, capsfilter, level, audioresample, audioconvert - ) + source.link(resample) + resample.link(convert) + convert.link(capsfilter) + capsfilter.link(level) - gst.element_link_many( - source, capsfilter, level, audioresample, audioconvert - ) + bin.add_pad(Gst.GhostPad.new('src', level.get_static_pad('src'))) return bin def build_encoder(self): - bin = gst.Bin('encoder') + self.logger.debug('Building encoder bin') + bin = Gst.Bin('encoder') # Encoding and payloading if self.link_config.encoding == 'opus': - encoder = gst.element_factory_make("opusenc", "encoder") + encoder = Gst.ElementFactory.make('opusenc', 'encoder') encoder.set_property('bitrate', int(self.link_config.bitrate) * 1000) encoder.set_property('tolerance', 80000000) encoder.set_property('frame-size', self.link_config.opus_framesize) @@ -129,62 +142,64 @@ def build_encoder(self): encoder.set_property('inband-fec', self.link_config.opus_fec) encoder.set_property('packet-loss-percentage', int(self.link_config.opus_loss_expectation)) encoder.set_property('dtx', self.link_config.opus_dtx) - print(encoder.get_properties('bitrate', 'dtx', 'inband-fec')) - payloader = gst.element_factory_make("rtpopuspay", "payloader") + + payloader = Gst.ElementFactory.make('rtpopuspay', 'payloader') elif self.link_config.encoding == 'pcm': # we have no encoder for PCM operation - payloader = gst.element_factory_make("rtpL16pay", "payloader") + payloader = Gst.ElementFactory.make('rtpL16pay', 'payloader') else: - self.logger.critical("Unknown encoding type %s" % self.link_config.encoding) + self.logger.critical('Unknown encoding type %s' % self.link_config.encoding) - if self.link_config.encoding != 'pcm': - # Only add the encoder if we're not in PCM mode - bin.add(encoder) - gst.element_link_many(encoder, payloader) - bin.add(payloader) + if 'encoder' in locals(): + bin.add(encoder) + encoder.link(payloader) + bin.add_pad(Gst.GhostPad.new('sink', encoder.get_static_pad('sink'))) + else: + bin.add_pad(Gst.GhostPad.new('sink', payloader.get_static_pad('sink'))) + + bin.add_pad(Gst.GhostPad.new('src', payloader.get_static_pad('src'))) + return bin def build_transport(self): - bin = gst.Bin('transport') + self.logger.debug('Building RTP transport bin') + bin = Gst.Bin('transport') + + # Our RTP manager + rtpbin = Gst.ElementFactory.make('rtpbin') + rtpbin.set_property('latency', 0) + bin.add(rtpbin) # TODO: Add a tee here, and sort out creating multiple UDP sinks for multipath - # Now the RTP bits - # We'll send audio out on this - udpsink_rtpout = gst.element_factory_make("udpsink", "udpsink_rtp") - udpsink_rtpout.set_property('host', self.link_config.receiver_host) - udpsink_rtpout.set_property('port', self.link_config.port) + udpsink = Gst.ElementFactory.make('udpsink') + udpsink.set_property('host', self.link_config.receiver_host) + udpsink.set_property('port', self.link_config.port) self.logger.info('Set receiver to %s:%i' % (self.link_config.receiver_host, self.link_config.port)) if self.link_config.multicast: - udpsink_rtpout.set_property('auto_multicast', True) + udpsink.set_property('auto_multicast', True) self.logger.info('Multicast mode enabled') + bin.add(udpsink) + rtpbin.link_pads('send_rtp_src_0', udpsink, 'sink') - # Our RTP manager - rtpbin = gst.element_factory_make("gstrtpbin", "gstrtpbin") - rtpbin.set_property('latency', 0) + bin.add_pad(Gst.GhostPad.new('sink', rtpbin.get_request_pad('send_rtp_sink_0'))) + bin.add_pad(Gst.GhostPad.new('capspad', udpsink.get_static_pad('sink'))) - # And now the RTP bits - payloader.link_pads('src', rtpbin, 'send_rtp_sink_0') - rtpbin.link_pads('send_rtp_src_0', udpsink_rtpout, 'sink') - # self.udpsrc_rtcpin.link_pads('src', rtpbin, 'recv_rtcp_sink_0') - # # RTCP SRs - # rtpbin.link_pads('send_rtcp_src_0', self.udpsink_rtcpout, 'sink') - - bin.add(udpsink_rtpout, rtpbin) + return bin def on_message(self, bus, message): - if message.type == gst.MESSAGE_ELEMENT: + if message.type == Gst.Message.ELEMENT: if message.structure.get_name() == 'level': if self.started is False: self.started = True - #gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, gst.DEBUG_GRAPH_SHOW_ALL, 'tx-graph') + #Gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, Gst.DEBUG_GRAPH_SHOW_ALL, 'tx-graph') #self.logger.debug(source.get_property('actual-buffer-time')) if len(message.structure['peak']) == 1: - self.logger.info("Started mono audio transmission") + self.logger.info('Started mono audio transmission') else: - self.logger.info("Started stereo audio transmission") + self.logger.info('Started stereo audio transmission') return True def get_caps(self): From 1fb6acd57e59868a2562c9c8b67f56f6d1823eab Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 14:00:32 +0100 Subject: [PATCH 05/25] Finish refactoring tx class to Gst 1.0 --- openob/rtp/tx.py | 56 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index c5b2702..88e167c 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -1,6 +1,7 @@ import gi gi.require_version('Gst', '1.0') -from gi.repository import Gst +from gi.repository import Gst, GLib, GObject +GObject.threads_init() Gst.init(None) import time @@ -25,9 +26,11 @@ def __init__(self, node_name, link_config, audio_interface): def run(self): self.pipeline.set_state(Gst.State.PLAYING) + # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') + while self.caps == 'None': self.caps = str( - self.transport.get_static_pad('sink').get_property('caps')) + self.transport.get_by_name('udpsink').get_static_pad('sink').get_property('caps')) if self.caps == 'None': self.logger.warn('Waiting for audio interface/caps') @@ -36,8 +39,8 @@ def run(self): def loop(self): try: - self.loop = gobject.MainLoop() - self.loop.run() + loop = GLib.MainLoop() + loop.run() except Exception as e: self.logger.exception('Encountered a problem in the MainLoop, tearing down the pipeline: %s' % e) self.pipeline.set_state(Gst.State.NULL) @@ -98,19 +101,13 @@ def build_audio_interface(self): # Add a capsfilter to allow specification of input sample rate capsfilter = Gst.ElementFactory.make('capsfilter') - # Decide which format to apply to the capsfilter (Jack uses float) - if self.audio_interface.type == 'jack': - data_type = 'audio/x-raw-float' - else: - data_type = 'audio/x-raw-int' + caps = source.get_static_pad('src').get_property('caps') # if audio_rate has been specified, then add that to the capsfilter if self.audio_interface.samplerate != 0: - capsfilter.set_property( - 'caps', Gst.Caps('%s, channels=2, rate=%d' % (data_type, self.audio_interface.samplerate))) - else: - capsfilter.set_property( - 'caps', Gst.Caps('%s, channels=2' % data_type)) + caps.set_value('rate', self.audio_interface.samplerate) + + capsfilter.set_property('caps', caps) bin.add(capsfilter) # Our level monitor @@ -168,12 +165,12 @@ def build_transport(self): bin = Gst.Bin('transport') # Our RTP manager - rtpbin = Gst.ElementFactory.make('rtpbin') + rtpbin = Gst.ElementFactory.make('rtpbin', 'rtpbin') rtpbin.set_property('latency', 0) bin.add(rtpbin) # TODO: Add a tee here, and sort out creating multiple UDP sinks for multipath - udpsink = Gst.ElementFactory.make('udpsink') + udpsink = Gst.ElementFactory.make('udpsink', 'udpsink') udpsink.set_property('host', self.link_config.receiver_host) udpsink.set_property('port', self.link_config.port) self.logger.info('Set receiver to %s:%i' % (self.link_config.receiver_host, self.link_config.port)) @@ -182,24 +179,29 @@ def build_transport(self): udpsink.set_property('auto_multicast', True) self.logger.info('Multicast mode enabled') bin.add(udpsink) - rtpbin.link_pads('send_rtp_src_0', udpsink, 'sink') bin.add_pad(Gst.GhostPad.new('sink', rtpbin.get_request_pad('send_rtp_sink_0'))) - bin.add_pad(Gst.GhostPad.new('capspad', udpsink.get_static_pad('sink'))) + + rtpbin.link_pads('send_rtp_src_0', udpsink, 'sink') return bin def on_message(self, bus, message): - if message.type == Gst.Message.ELEMENT: - if message.structure.get_name() == 'level': - if self.started is False: - self.started = True - #Gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, Gst.DEBUG_GRAPH_SHOW_ALL, 'tx-graph') - #self.logger.debug(source.get_property('actual-buffer-time')) - if len(message.structure['peak']) == 1: - self.logger.info('Started mono audio transmission') + if message.type == Gst.MessageType.ELEMENT: + struct = message.get_structure() + if struct != None: + if struct.get_name() == 'level': + if self.started is False: + self.started = True + if len(struct['peak']) == 1: + self.logger.info('Started mono audio transmission') + else: + self.logger.info('Started stereo audio transmission') else: - self.logger.info('Started stereo audio transmission') + if len(struct['peak']) == 1: + self.logger.debug('Level: %d', struct['peak'][0]) + else: + self.logger.debug('Levels: L %d R %d' % struct['peak'][1]) return True def get_caps(self): From c306138187a5bba77bad108b7b274b466efb3444 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 14:06:22 +0100 Subject: [PATCH 06/25] remove unnecessary imports from tx class --- openob/rtp/tx.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index 88e167c..06c318d 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -1,15 +1,11 @@ import gi gi.require_version('Gst', '1.0') -from gi.repository import Gst, GLib, GObject -GObject.threads_init() +from gi.repository import Gst, GLib Gst.init(None) import time -import re -import sys from openob.logger import LoggerFactory - class RTPTransmitter(object): def __init__(self, node_name, link_config, audio_interface): From 5ea54cd69a17bfb46237aba1fb24092b87ad1855 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 15:49:59 +0100 Subject: [PATCH 07/25] remove superfluous bus connect --- openob/rtp/tx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index 06c318d..a8792f1 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -48,7 +48,6 @@ def build_pipeline(self): self.caps = 'None' bus = self.pipeline.get_bus() - bus.connect('message', self.on_message) self.source = self.build_audio_interface() self.encoder = self.build_encoder() From f49fc872d12f3899d4f2ca87e2e66538192ed944 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 15:50:05 +0100 Subject: [PATCH 08/25] add test sink for rx side --- bin/openob | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/openob b/bin/openob index 81d1799..e10ecac 100755 --- a/bin/openob +++ b/bin/openob @@ -45,7 +45,7 @@ parser_tx_opus.add_argument('--framesize', type=int, default=20, help="Opus fram parser_tx.set_defaults(mode='tx', fec=True, dtx=False, multicast=False) parser_rx = subparsers.add_parser('rx', formatter_class=argparse.ArgumentDefaultsHelpFormatter) -parser_rx.add_argument('-a', '--audio_output', type=str, choices=['auto', 'alsa', 'jack'], default='auto', help="The audio output type for this end of the link") +parser_rx.add_argument('-a', '--audio_output', type=str, choices=['auto', 'alsa', 'jack', 'test'], default='auto', help="The audio output type for this end of the link") parser_rx_alsa = parser_rx.add_argument_group('alsa', 'Options when using ALSA output type') parser_rx_alsa.add_argument('-d', '--alsa_device', type=str, default='hw:0', help="The ALSA device to connect to for input") parser_rx_jack = parser_rx.add_argument_group('jack', 'Options when using JACK output type') From 6155a322e238f3741442ce63963a885316c257dc Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 15:50:15 +0100 Subject: [PATCH 09/25] initial refactoring of RX class; doesn't work yet --- openob/rtp/rx.py | 248 +++++++++++++++++++++++++++++------------------ 1 file changed, 152 insertions(+), 96 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 154485f..69bc970 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -1,135 +1,191 @@ +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst, GLib +Gst.init(None) - -import re from openob.logger import LoggerFactory - class RTPReceiver(object): def __init__(self, node_name, link_config, audio_interface): """Sets up a new RTP receiver""" - self.started = False - self.pipeline = gst.Pipeline("rx") - self.bus = self.pipeline.get_bus() - self.bus.connect("message", self.on_message) + self.link_config = link_config self.audio_interface = audio_interface + self.logger_factory = LoggerFactory() self.logger = self.logger_factory.getLogger('node.%s.link.%s.%s' % (node_name, self.link_config.name, self.audio_interface.mode)) - self.logger.info('Creating RTP reception pipeline') - caps = self.link_config.get("caps") + self.logger.info('Creating reception pipeline') + + self.build_pipeline() + + def run(self): + self.pipeline.set_state(Gst.State.PLAYING) + Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') + + self.logger.info('Listening for stream on %s:%i' % (self.link_config.receiver_host, self.link_config.port)) + + def loop(self): + try: + self.loop = GLib.MainLoop() + self.loop.run() + except Exception as e: + self.logger.exception('Encountered a problem in the MainLoop, tearing down the pipeline: %s' % e) + self.pipeline.set_state(Gst.State.NULL) + + def build_pipeline(self): + self.pipeline = Gst.Pipeline('rx') + + self.started = False + bus = self.pipeline.get_bus() + + self.transport = self.build_transport() + self.decoder = self.build_decoder() + self.output = self.build_audio_interface() + + self.pipeline.add(self.transport, self.decoder, self.output) + self.transport.link(self.decoder) + self.decoder.link(self.output) + + bus.add_signal_watch() + bus.connect('message', self.on_message) + + def build_audio_interface(self): + self.logger.debug('Building audio output bin') + bin = Gst.Bin('audio') + # Audio output if self.audio_interface.type == 'auto': - self.sink = gst.element_factory_make("autoaudiosink") + sink = Gst.ElementFactory.make('autoaudiosink') elif self.audio_interface.type == 'alsa': - self.sink = gst.element_factory_make("alsasink") - self.sink.set_property('device', self.audio_interface.alsa_device) + sink = Gst.ElementFactory.make('alsasink') + sink.set_property('device', self.audio_interface.alsa_device) elif self.audio_interface.type == 'jack': - self.sink = gst.element_factory_make("jackaudiosink") + sink = Gst.ElementFactory.make('jackaudiosink') if self.audio_interface.jack_auto: - self.sink.set_property('connect', 'auto') + sink.set_property('connect', 'auto') else: - self.sink.set_property('connect', 'none') - self.sink.set_property('name', self.audio_interface.jack_name) - self.sink.set_property('client-name', self.audio_interface.jack_name) - + sink.set_property('connect', 'none') + sink.set_property('name', self.audio_interface.jack_name) + sink.set_property('client-name', self.audio_interface.jack_name) + elif self.audio_interface.type == 'test': + sink = Gst.ElementFactory.make('fakesink') + + bin.add(sink) + # Audio resampling and conversion - self.audioresample = gst.element_factory_make("audioresample") - self.audioconvert = gst.element_factory_make("audioconvert") - self.audioresample.set_property('quality', 6) + resample = Gst.ElementFactory.make('audioresample') + resample.set_property('quality', 9) + bin.add(resample) + + convert = Gst.ElementFactory.make('audioconvert') + bin.add(convert) + + # Our level monitor, also used for continuous audio + level = Gst.ElementFactory.make('level') + level.set_property('message', True) + level.set_property('interval', 1000000000) + bin.add(level) + + resample.link(convert) + convert.link(level) + level.link(sink) + + bin.add_pad(Gst.GhostPad.new('sink', resample.get_static_pad('sink'))) + + return bin + + def build_decoder(self): + self.logger.debug('Building decoder bin') + bin = Gst.Bin('decoder') # Decoding and depayloading if self.link_config.encoding == 'opus': - self.decoder = gst.element_factory_make("opusdec", "decoder") - self.decoder.set_property('use-inband-fec', True) # FEC - self.decoder.set_property('plc', True) # Packet loss concealment - self.depayloader = gst.element_factory_make( - "rtpopusdepay", "depayloader") + decoder = Gst.ElementFactory.make('opusdec', 'decoder') + decoder.set_property('use-inband-fec', True) # FEC + decoder.set_property('plc', True) # Packet loss concealment + depayloader = Gst.ElementFactory.make( + 'rtpopusdepay', 'depayloader') elif self.link_config.encoding == 'pcm': - self.depayloader = gst.element_factory_make( - "rtpL16depay", "depayloader") - - # RTP stuff - self.rtpbin = gst.element_factory_make('gstrtpbin') - self.rtpbin.set_property('latency', self.link_config.jitter_buffer) - self.rtpbin.set_property('autoremove', True) - self.rtpbin.set_property('do-lost', True) - #self.rtpbin.set_property('buffer-mode', 1) + depayloader = Gst.ElementFactory.make( + 'rtpL16depay', 'depayloader') + else: + self.logger.critical('Unknown encoding type %s' % self.link_config.encoding) + + bin.add(depayloader) + + bin.add_pad(Gst.GhostPad.new('sink', depayloader.get_static_pad('sink'))) + + if 'decoder' in locals(): + bin.add(decoder) + depayloader.link(decoder) + bin.add_pad(Gst.GhostPad.new('src', decoder.get_static_pad('src'))) + else: + bin.add_pad(Gst.GhostPad.new('src', decoder.get_static_pad('src'))) + + return bin + + def build_transport(self): + self.logger.debug('Building RTP transport bin') + bin = Gst.Bin('transport') + # Where audio comes in - self.udpsrc_rtpin = gst.element_factory_make('udpsrc') - self.udpsrc_rtpin.set_property('port', self.link_config.port) + udpsrc = Gst.ElementFactory.make('udpsrc', 'udpsrc') + udpsrc.set_property('port', self.link_config.port) if self.link_config.multicast: - self.udpsrc_rtpin.set_property('auto_multicast', True) - self.udpsrc_rtpin.set_property('multicast_group', self.link_config.receiver_host) + udpsrc.set_property('auto_multicast', True) + udpsrc.set_property('multicast_group', self.link_config.receiver_host) self.logger.info('Multicast mode enabled') - caps = caps.replace('\\', '') - # Fix for gstreamer bug in rtpopuspay fixed in GST-plugins-bad - # 50140388d2b62d32dd9d0c071e3051ebc5b4083b, bug 686547 - if self.link_config.encoding == 'opus': - caps = re.sub(r'(caps=.+ )', '', caps) - udpsrc_caps = gst.caps_from_string(caps) - self.udpsrc_rtpin.set_property('caps', udpsrc_caps) - self.udpsrc_rtpin.set_property('timeout', 3000000) + bin.add(udpsrc) - # Our level monitor, also used for continuous audio - self.level = gst.element_factory_make("level") - self.level.set_property('message', True) - self.level.set_property('interval', 1000000000) - - # And now we've got it all set up we need to add the elements - self.pipeline.add( - self.audioconvert, self.audioresample, self.sink, - self.level, self.depayloader, self.rtpbin, self.udpsrc_rtpin) - if self.link_config.encoding != 'pcm': - self.pipeline.add(self.decoder) - gst.element_link_many( - self.depayloader, self.decoder, self.audioconvert) - else: - gst.element_link_many(self.depayloader, self.audioconvert) - gst.element_link_many( - self.audioconvert, self.audioresample, self.level, - self.sink) - self.logger.debug(self.sink) - # Now the RTP pads - self.udpsrc_rtpin.link_pads('src', self.rtpbin, 'recv_rtp_sink_0') + rtpbin = Gst.ElementFactory.make('rtpbin', 'rtpbin') + rtpbin.set_property('latency', self.link_config.jitter_buffer) + rtpbin.set_property('autoremove', True) + rtpbin.set_property('do-lost', True) + bin.add(rtpbin) + + caps = self.link_config.get('caps').replace('\\', '') + udpsrc_caps = Gst.Caps.from_string(caps) + + udpsrc.set_property('caps', udpsrc_caps) + udpsrc.set_property('timeout', 3000000) + + udpsrc.link_pads('src', rtpbin, 'recv_rtp_sink_0') + bin.add_pad(Gst.GhostPad.new_no_target('src', Gst.PadDirection.SRC)) # Attach callbacks for dynamic pads (RTP output) and busses - self.rtpbin.connect('pad-added', self.rtpbin_pad_added) - self.bus.add_signal_watch() + rtpbin.connect('pad-added', self.rtpbin_pad_added) + + return bin # Our RTPbin won't give us an audio pad till it receives, so we need to # attach it here def rtpbin_pad_added(self, obj, pad): + ghost_pad = self.transport.get_static_pad('src') # Unlink first. - self.rtpbin.unlink(self.depayloader) + pad.unlink(ghost_pad) # Relink - self.rtpbin.link(self.depayloader) + pad.link(ghost_pad) def on_message(self, bus, message): - if message.type == gst.MESSAGE_ELEMENT: - if message.structure.get_name() == 'level': - if self.started is False: - self.started = True - #gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, gst.DEBUG_GRAPH_SHOW_ALL, 'rx-graph') - if len(message.structure['peak']) == 1: - self.logger.info("Receiving mono audio transmission") - else: - self.logger.info("Receiving stereo audio transmission") - - if message.structure.get_name() == 'GstUDPSrcTimeout': - # Only UDP source configured to emit timeouts is the audio - # input - self.logger.critical("No data received for 3 seconds!") - if self.started: - self.logger.critical("Shutting down receiver for restart") - self.pipeline.set_state(gst.STATE_NULL) - self.loop.quit() + if message.type == Gst.MessageType.ELEMENT: + struct = message.get_structure() + if struct != None: + if struct.get_name() == 'level': + if self.started is False: + self.started = True + if len(struct['peak']) == 1: + self.logger.info('Receiving mono audio transmission') + else: + self.logger.info('Receiving stereo audio transmission') + + if struct.get_name() == 'GstUDPSrcTimeout': + # Only UDP source configured to emit timeouts is the audio input + self.logger.critical('No data received for 3 seconds!') + if self.started: + self.logger.critical('Shutting down receiver for restart') + self.pipeline.set_state(Gst.State.NULL) + self.loop.quit() return True - def run(self): - self.pipeline.set_state(gst.STATE_PLAYING) - self.logger.info('Listening for stream on %s:%i' % (self.link_config.receiver_host, self.link_config.port)) - def loop(self): - self.loop = gobject.MainLoop() - self.loop.run() From bcaceb124f087cc622200d8ff6d5b2dfab5ed8a4 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 16:32:05 +0100 Subject: [PATCH 10/25] RX now runs correctly --- openob/rtp/rx.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 69bc970..b3fbd3b 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -21,8 +21,6 @@ def __init__(self, node_name, link_config, audio_interface): def run(self): self.pipeline.set_state(Gst.State.PLAYING) - Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') - self.logger.info('Listening for stream on %s:%i' % (self.link_config.receiver_host, self.link_config.port)) def loop(self): @@ -128,10 +126,15 @@ def build_decoder(self): def build_transport(self): self.logger.debug('Building RTP transport bin') bin = Gst.Bin('transport') + + caps = self.link_config.get('caps').replace('\\', '') + udpsrc_caps = Gst.Caps.from_string(caps) # Where audio comes in udpsrc = Gst.ElementFactory.make('udpsrc', 'udpsrc') udpsrc.set_property('port', self.link_config.port) + udpsrc.set_property('caps', udpsrc_caps) + udpsrc.set_property('timeout', 3000000000) if self.link_config.multicast: udpsrc.set_property('auto_multicast', True) udpsrc.set_property('multicast_group', self.link_config.receiver_host) @@ -144,15 +147,12 @@ def build_transport(self): rtpbin.set_property('do-lost', True) bin.add(rtpbin) - caps = self.link_config.get('caps').replace('\\', '') - udpsrc_caps = Gst.Caps.from_string(caps) - - udpsrc.set_property('caps', udpsrc_caps) - udpsrc.set_property('timeout', 3000000) - udpsrc.link_pads('src', rtpbin, 'recv_rtp_sink_0') - bin.add_pad(Gst.GhostPad.new_no_target('src', Gst.PadDirection.SRC)) + valve = Gst.ElementFactory.make('valve', 'valve') + bin.add(valve) + + bin.add_pad(Gst.GhostPad.new('src', valve.get_static_pad('src'))) # Attach callbacks for dynamic pads (RTP output) and busses rtpbin.connect('pad-added', self.rtpbin_pad_added) @@ -161,11 +161,13 @@ def build_transport(self): # Our RTPbin won't give us an audio pad till it receives, so we need to # attach it here def rtpbin_pad_added(self, obj, pad): - ghost_pad = self.transport.get_static_pad('src') + valve = self.transport.get_by_name('valve') + rtpbin = self.transport.get_by_name('rtpbin') + # Unlink first. - pad.unlink(ghost_pad) + rtpbin.unlink(valve) # Relink - pad.link(ghost_pad) + rtpbin.link(valve) def on_message(self, bus, message): if message.type == Gst.MessageType.ELEMENT: @@ -178,8 +180,14 @@ def on_message(self, bus, message): self.logger.info('Receiving mono audio transmission') else: self.logger.info('Receiving stereo audio transmission') + else: + if len(struct['peak']) == 1: + self.logger.debug('Level: %d', struct['peak'][0]) + else: + self.logger.debug('Levels: L %d R %d' % struct['peak'][1]) if struct.get_name() == 'GstUDPSrcTimeout': + Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') # Only UDP source configured to emit timeouts is the audio input self.logger.critical('No data received for 3 seconds!') if self.started: From 234815ec66fa8c6d25c9ccec8eb70e9e30610969 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 16:49:41 +0100 Subject: [PATCH 11/25] not much point formatting decibels as integers --- openob/rtp/rx.py | 4 ++-- openob/rtp/tx.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index b3fbd3b..1ad900e 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -182,9 +182,9 @@ def on_message(self, bus, message): self.logger.info('Receiving stereo audio transmission') else: if len(struct['peak']) == 1: - self.logger.debug('Level: %d', struct['peak'][0]) + self.logger.debug('Level: %.2f', struct['peak'][0]) else: - self.logger.debug('Levels: L %d R %d' % struct['peak'][1]) + self.logger.debug('Levels: L %.2f R %.2f' % (struct['peak'][0], struct['peak'][1])) if struct.get_name() == 'GstUDPSrcTimeout': Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index a8792f1..94c0eed 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -194,9 +194,9 @@ def on_message(self, bus, message): self.logger.info('Started stereo audio transmission') else: if len(struct['peak']) == 1: - self.logger.debug('Level: %d', struct['peak'][0]) + self.logger.debug('Level: %.2f', struct['peak'][0]) else: - self.logger.debug('Levels: L %d R %d' % struct['peak'][1]) + self.logger.debug('Levels: L %.2f R %.2f' % (struct['peak'][0], struct['peak'][1])) return True def get_caps(self): From 5483c597889c8f5cf8a123f64efa9649d23897ce Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 16:50:18 +0100 Subject: [PATCH 12/25] name conflict with def loop and self.loop --- openob/rtp/rx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 1ad900e..02f9d00 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -25,8 +25,8 @@ def run(self): def loop(self): try: - self.loop = GLib.MainLoop() - self.loop.run() + self.main_loop = GLib.MainLoop() + self.main_loop.run() except Exception as e: self.logger.exception('Encountered a problem in the MainLoop, tearing down the pipeline: %s' % e) self.pipeline.set_state(Gst.State.NULL) @@ -193,7 +193,7 @@ def on_message(self, bus, message): if self.started: self.logger.critical('Shutting down receiver for restart') self.pipeline.set_state(Gst.State.NULL) - self.loop.quit() + self.main_loop.quit() return True From 6adb356751248a715c7af7c30dff7cf7cf4b0e71 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 19:42:00 +0100 Subject: [PATCH 13/25] Fix PCM by moving the level element up to right after the audiosrc --- openob/rtp/rx.py | 2 +- openob/rtp/tx.py | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 02f9d00..37fc2a3 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -119,7 +119,7 @@ def build_decoder(self): depayloader.link(decoder) bin.add_pad(Gst.GhostPad.new('src', decoder.get_static_pad('src'))) else: - bin.add_pad(Gst.GhostPad.new('src', decoder.get_static_pad('src'))) + bin.add_pad(Gst.GhostPad.new('src', depayloader.get_static_pad('src'))) return bin diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index 94c0eed..e571452 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -22,7 +22,7 @@ def __init__(self, node_name, link_config, audio_interface): def run(self): self.pipeline.set_state(Gst.State.PLAYING) - # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') + Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') while self.caps == 'None': self.caps = str( @@ -85,6 +85,12 @@ def build_audio_interface(self): bin.add(source) + # Our level monitor + level = Gst.ElementFactory.make('level') + level.set_property('message', True) + level.set_property('interval', 1000000000) + bin.add(level) + # Audio resampling and conversion resample = Gst.ElementFactory.make('audioresample') resample.set_property('quality', 9) # SRC @@ -96,27 +102,22 @@ def build_audio_interface(self): # Add a capsfilter to allow specification of input sample rate capsfilter = Gst.ElementFactory.make('capsfilter') - caps = source.get_static_pad('src').get_property('caps') + caps = Gst.Caps.new_empty_simple('audio/x-raw') # if audio_rate has been specified, then add that to the capsfilter if self.audio_interface.samplerate != 0: caps.set_value('rate', self.audio_interface.samplerate) + self.logger.debug(caps.to_string()) capsfilter.set_property('caps', caps) bin.add(capsfilter) - # Our level monitor - level = Gst.ElementFactory.make('level') - level.set_property('message', True) - level.set_property('interval', 1000000000) - bin.add(level) - - source.link(resample) + source.link(level) + level.link(resample) resample.link(convert) convert.link(capsfilter) - capsfilter.link(level) - bin.add_pad(Gst.GhostPad.new('src', level.get_static_pad('src'))) + bin.add_pad(Gst.GhostPad.new('src', capsfilter.get_static_pad('src'))) return bin From 561b2bdac02da7592663a7c005f829c6d75300f5 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 19:44:01 +0100 Subject: [PATCH 14/25] disable debug dotfile generation --- openob/rtp/tx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index e571452..b6bc2c8 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -22,7 +22,7 @@ def __init__(self, node_name, link_config, audio_interface): def run(self): self.pipeline.set_state(Gst.State.PLAYING) - Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') + # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') while self.caps == 'None': self.caps = str( From 1543d36df0d3c75202c2d1c0f61eecc19feff8ed Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 19:44:22 +0100 Subject: [PATCH 15/25] disable debug dotfile generation --- openob/rtp/rx.py | 2 +- openob/rtp/tx.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 37fc2a3..3f861c6 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -187,7 +187,7 @@ def on_message(self, bus, message): self.logger.debug('Levels: L %.2f R %.2f' % (struct['peak'][0], struct['peak'][1])) if struct.get_name() == 'GstUDPSrcTimeout': - Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') + # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') # Only UDP source configured to emit timeouts is the audio input self.logger.critical('No data received for 3 seconds!') if self.started: diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index e571452..b6bc2c8 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -22,7 +22,7 @@ def __init__(self, node_name, link_config, audio_interface): def run(self): self.pipeline.set_state(Gst.State.PLAYING) - Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') + # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') while self.caps == 'None': self.caps = str( From 37cf685f8a86f187d70585059ad14d4cd361e9e3 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 22 May 2018 20:20:03 +0100 Subject: [PATCH 16/25] python 3 compat - mostly redis byte decoding --- openob/link_config.py | 2 +- openob/rtp/rx.py | 20 +++++++++++--------- openob/rtp/tx.py | 32 +++++++++++++++++--------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/openob/link_config.py b/openob/link_config.py index a392cf6..8c221a8 100755 --- a/openob/link_config.py +++ b/openob/link_config.py @@ -27,7 +27,7 @@ def __init__(self, link_name, redis_host): self.redis = None while True: try: - self.redis = redis.StrictRedis(self.redis_host) + self.redis = redis.StrictRedis(host=self.redis_host, charset="utf-8", decode_responses=True) break except Exception as e: self.logger.error( diff --git a/openob/rtp/rx.py b/openob/rtp/rx.py index 3f861c6..3771533 100755 --- a/openob/rtp/rx.py +++ b/openob/rtp/rx.py @@ -32,7 +32,7 @@ def loop(self): self.pipeline.set_state(Gst.State.NULL) def build_pipeline(self): - self.pipeline = Gst.Pipeline('rx') + self.pipeline = Gst.Pipeline.new('rx') self.started = False bus = self.pipeline.get_bus() @@ -41,7 +41,9 @@ def build_pipeline(self): self.decoder = self.build_decoder() self.output = self.build_audio_interface() - self.pipeline.add(self.transport, self.decoder, self.output) + self.pipeline.add(self.transport) + self.pipeline.add(self.decoder) + self.pipeline.add(self.output) self.transport.link(self.decoder) self.decoder.link(self.output) @@ -50,7 +52,7 @@ def build_pipeline(self): def build_audio_interface(self): self.logger.debug('Building audio output bin') - bin = Gst.Bin('audio') + bin = Gst.Bin.new('audio') # Audio output if self.audio_interface.type == 'auto': @@ -95,7 +97,7 @@ def build_audio_interface(self): def build_decoder(self): self.logger.debug('Building decoder bin') - bin = Gst.Bin('decoder') + bin = Gst.Bin.new('decoder') # Decoding and depayloading if self.link_config.encoding == 'opus': @@ -125,7 +127,7 @@ def build_decoder(self): def build_transport(self): self.logger.debug('Building RTP transport bin') - bin = Gst.Bin('transport') + bin = Gst.Bin.new('transport') caps = self.link_config.get('caps').replace('\\', '') udpsrc_caps = Gst.Caps.from_string(caps) @@ -176,15 +178,15 @@ def on_message(self, bus, message): if struct.get_name() == 'level': if self.started is False: self.started = True - if len(struct['peak']) == 1: + if len(struct.get_value('peak')) == 1: self.logger.info('Receiving mono audio transmission') else: self.logger.info('Receiving stereo audio transmission') else: - if len(struct['peak']) == 1: - self.logger.debug('Level: %.2f', struct['peak'][0]) + if len(struct.get_value('peak')) == 1: + self.logger.debug('Level: %.2f', struct.get_value('peak')[0]) else: - self.logger.debug('Levels: L %.2f R %.2f' % (struct['peak'][0], struct['peak'][1])) + self.logger.debug('Levels: L %.2f R %.2f' % (struct.get_value('peak')[0], struct.get_value('peak')[1])) if struct.get_name() == 'GstUDPSrcTimeout': # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'rx-graph') diff --git a/openob/rtp/tx.py b/openob/rtp/tx.py index b6bc2c8..90481e8 100755 --- a/openob/rtp/tx.py +++ b/openob/rtp/tx.py @@ -24,14 +24,14 @@ def run(self): self.pipeline.set_state(Gst.State.PLAYING) # Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'tx-graph') - while self.caps == 'None': - self.caps = str( - self.transport.get_by_name('udpsink').get_static_pad('sink').get_property('caps')) + while self.caps == None: + caps = self.transport.get_by_name('udpsink').get_static_pad('sink').get_property('caps') - if self.caps == 'None': + if caps == None: self.logger.warn('Waiting for audio interface/caps') - - time.sleep(0.1) + time.sleep(0.1) + else: + self.caps = caps.to_string() def loop(self): try: @@ -45,7 +45,7 @@ def build_pipeline(self): self.pipeline = Gst.Pipeline.new('tx') self.started = False - self.caps = 'None' + self.caps = None bus = self.pipeline.get_bus() @@ -53,7 +53,9 @@ def build_pipeline(self): self.encoder = self.build_encoder() self.transport = self.build_transport() - self.pipeline.add(self.source, self.encoder, self.transport) + self.pipeline.add(self.source) + self.pipeline.add(self.encoder) + self.pipeline.add(self.transport) self.source.link(self.encoder) self.encoder.link(self.transport) @@ -63,7 +65,7 @@ def build_pipeline(self): def build_audio_interface(self): self.logger.debug('Building audio input bin') - bin = Gst.Bin('audio') + bin = Gst.Bin.new('audio') # Audio input if self.audio_interface.type == 'auto': @@ -123,7 +125,7 @@ def build_audio_interface(self): def build_encoder(self): self.logger.debug('Building encoder bin') - bin = Gst.Bin('encoder') + bin = Gst.Bin.new('encoder') # Encoding and payloading if self.link_config.encoding == 'opus': @@ -158,7 +160,7 @@ def build_encoder(self): def build_transport(self): self.logger.debug('Building RTP transport bin') - bin = Gst.Bin('transport') + bin = Gst.Bin.new('transport') # Our RTP manager rtpbin = Gst.ElementFactory.make('rtpbin', 'rtpbin') @@ -189,15 +191,15 @@ def on_message(self, bus, message): if struct.get_name() == 'level': if self.started is False: self.started = True - if len(struct['peak']) == 1: + if len(struct.get_value('peak')) == 1: self.logger.info('Started mono audio transmission') else: self.logger.info('Started stereo audio transmission') else: - if len(struct['peak']) == 1: - self.logger.debug('Level: %.2f', struct['peak'][0]) + if len(struct.get_value('peak')) == 1: + self.logger.debug('Level: %.2f', struct.get_value('peak')[0]) else: - self.logger.debug('Levels: L %.2f R %.2f' % (struct['peak'][0], struct['peak'][1])) + self.logger.debug('Levels: L %.2f R %.2f' % (struct.get_value('peak')[0], struct.get_value('peak')[1])) return True def get_caps(self): From 5fed5b97e67e98ac2771a3422fb348ad28308b72 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Mon, 9 Jul 2018 21:24:33 +0100 Subject: [PATCH 17/25] Add .travis.yml and update changelog --- .travis.yml | 31 +++++++++++++++++++++++++++++++ CHANGELOG.md | 5 +++++ 2 files changed, 36 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b28b4bb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +sudo: false + +language: python + +addons: + apt: + packages: + - python-gst-1.0 + - gstreamer1.0-plugins-base + - gstreamer1.0-plugins-good + - python-gobject + - python-redis + - python-gi + +services: + - redis-server + +python: + - 2.7 + - 3.6 + +install: + - python setup.py install + +script: + - (openob 127.0.0.1 -v travis_1 travis tx 127.0.0.1 -a test 2>&1 | tee tx_log) & + - (tail -f -n0 tx_log &) | grep -q "Started mono audio transmission" + - (openob 127.0.0.1 -v travis_2 travis rx -a test 2>&1 | tee rx_log) & + - (tail -f -n0 tx_log &) | grep -q "Recieving mono audio transmission" + - sleep 10 + - pkill openob \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 243fb7d..de7b88c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.0.0-dev + +* Upgraded GStreamer libraries to ^1.0 +* Added Python 3 compatability + ## 3.1 * Improved command line interface (Jonty Sewell) From 150c5b5a4e85f0bb2735130d7c2a7cc89bd15a10 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Mon, 9 Jul 2018 22:17:53 +0100 Subject: [PATCH 18/25] fix travis.yml for system packages in virtualenv --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b28b4bb..8b26727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,7 @@ addons: apt: packages: - python-gst-1.0 - - gstreamer1.0-plugins-base - - gstreamer1.0-plugins-good + - gstreamer1.0 - python-gobject - python-redis - python-gi @@ -17,7 +16,10 @@ services: python: - 2.7 - - 3.6 + - 3.4 + +virtualenv: + system_site_packages: true install: - python setup.py install From 9c21d99b96f223a01b2b32c0cf9a0e2ff7150fb2 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 12:21:32 +0100 Subject: [PATCH 19/25] Gst1.0 no longer messes with argv apparently --- bin/openob | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bin/openob b/bin/openob index e10ecac..64ee4c2 100755 --- a/bin/openob +++ b/bin/openob @@ -1,15 +1,13 @@ -#!/usr/bin/python +#!/usr/bin/env python + import sys import argparse import logging -# Thanks gst for messing with argv -argv = sys.argv -sys.argv = [] + from openob.logger import LoggerFactory from openob.node import Node from openob.link_config import LinkConfig from openob.audio_interface import AudioInterface -sys.argv = argv parser = argparse.ArgumentParser(prog='openob', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-v', '--verbose', action='store_const', help='Increase logging verbosity', const=logging.DEBUG, default=logging.INFO) From f3aacc6735189e75cb6d6aeb6447c1d1f2f2c1f0 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 12:39:27 +0100 Subject: [PATCH 20/25] Update documentation for gstreamer1.0 packages --- doc/source/conf.py | 4 ++-- doc/source/index.rst | 4 ---- doc/source/intro.rst | 4 ++-- doc/source/tutorial.rst | 13 +++++++------ 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index eb9bec8..c74f39a 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -48,9 +48,9 @@ # built documents. # # The short X.Y version. -version = '3.0' +version = '4.0' # The full version, including alpha/beta/rc tags. -release = '3.0 alpha2' +release = '4.0.0-dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/index.rst b/doc/source/index.rst index 63f13b8..9fd03e6 100755 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -14,10 +14,6 @@ OpenOB can be used for: It can be used on a variety of network connections (including over the internet and mobile links such as 3G), with operating bitrates as low as 16kbps in compressed mode, and support for fully lossless operation in linear PCM mode. -.. WARNING:: - This is the documentation for the currently-unreleased/unstable OpenOB 3 refactoring. If you are using the 2.3 version from PyPi some documentation may be incorrect or misleading. - - Documentation Index =================== diff --git a/doc/source/intro.rst b/doc/source/intro.rst index 4314e84..2ee4837 100755 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -10,7 +10,7 @@ Architecture OpenOB is a peer to peer audio streaming system with a central configuration server. -The program itself is a set of Python classes wrapping the PyGST bindings for the GStreamer media framework, which itself performs the audio encoding/decoding and transmission. +The program itself is a set of Python classes wrapping the Python GObject bindings for the GStreamer media framework, which itself performs the audio encoding/decoding and transmission. An OpenOB *link* is comprised of a receiver and transmitter pair. @@ -51,7 +51,7 @@ The following is a recommended set of specifications that are known to run OpenO - Dual-core Intel Atom, i3 or better @ 1.2GHz or better - 512MB RAM (2GB if you want a desktop environment) - 100Mbps NIC -- Debian Wheezy (7.0) +- Debian Jessie (8.0) OpenOB has been known to run on systems with significantly lower specifications. diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 0d28175..2c91427 100755 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -17,25 +17,26 @@ OpenOB relies on the GStreamer media framework for the underlying audio transpor Additionally, OpenOB needs some Python extensions, and on the configuration server, we must also install the Redis server used for configuration management. -On Debian you can install the prerequisites with the following command: +On Debian Stretch / Ubuntu Xenial you can install the prerequisites with the following command: .. code-block:: bash - sudo apt-get install python-gst0.10 python-setuptools gstreamer0.10-plugins-base gstreamer0.10-plugins-bad gstreamer0.10-plugins-good gstreamer0.10-plugins-ugly gstreamer0.10-tools python-gobject python-gobject-2 gstreamer0.10-alsa python-argparse python-redis + sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good gir1.2-gstreamer-1.0 python-gst-1.0 python-gobject python-redis python-gi python-setuptools -This should also work on Ubuntu. Your GStreamer implementation must be recent enough to support Opus; this is supported in Ubuntu 13.04 and Debian Wheezy or newer. In order to ensure compatibility, it is recommended that both ends of the link use the same version of GStreamer, which is most easily achieved by running the same operating system version on each end and installing the distribution's packages as detailed above. +The GStreamer Opus plugin has graduated from the 'bad' plugins repository to the 'base' repository as of 2015. Older distributions may require the `gstreamer1.0-plugins-bad` package installed. +In order to ensure compatibility, it is recommended that both ends of the link use the same version of GStreamer, which is most easily achieved by running the same operating system version on each end and installing the distribution's packages as detailed above. On one machine, which for this tutorial we'll assume is also our receiver, we'll install Redis: .. code-block:: bash - [user@rx-host] $ sudo apt-get install redis-server + [user@rx-host] $ sudo apt install redis-server We also need to make sure Redis binds itself to be accessible to remote machines, not just localhost. You can edit ``/etc/redis/redis.conf`` yourself or run the following to instantly make this adjustment .. code-block:: bash - [user@rx-host] $ sudo sed -i.bak ‘s/bind 127.*/bind 0.0.0.0/’ /etc/redis/redis.conf && sudo service redis restart + [user@rx-host] $ sudo sed -i.bak ‘s/bind 127.*/bind 0.0.0.0/’ /etc/redis/redis.conf && sudo service redis-server restart Installing OpenOB ----------------- @@ -44,7 +45,7 @@ Now we can install OpenOB itself. You can install from git for the bleeding edge .. code-block:: bash - sudo easy_install OpenOB + sudo pip install OpenOB Networking ---------- From 82674f077102825ac67cb26cb9cf8bdb9982856e Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 12:44:55 +0100 Subject: [PATCH 21/25] I can never spell receiving... --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8b26727..7eeaeb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,11 @@ language: python addons: apt: packages: + - gstreamer1.0-plugins-base # Latest gstreamer has Opus in base + - gstreamer1.0-plugins-good # RTP/UDP/audio plugins reside in good + - gstreamer1.0-plugins-bad # The version of gstreamer in Trusty still has Opus in bad + - gir1.2-gstreamer-1.0 - python-gst-1.0 - - gstreamer1.0 - python-gobject - python-redis - python-gi @@ -19,7 +22,7 @@ python: - 3.4 virtualenv: - system_site_packages: true + system_site_packages: true # Required as Travis runs python in a virtualenv, thus denying access to system GLib install: - python setup.py install @@ -28,6 +31,6 @@ script: - (openob 127.0.0.1 -v travis_1 travis tx 127.0.0.1 -a test 2>&1 | tee tx_log) & - (tail -f -n0 tx_log &) | grep -q "Started mono audio transmission" - (openob 127.0.0.1 -v travis_2 travis rx -a test 2>&1 | tee rx_log) & - - (tail -f -n0 tx_log &) | grep -q "Recieving mono audio transmission" + - (tail -f -n0 rx_log &) | grep -q "Receiving mono audio transmission" - sleep 10 - pkill openob \ No newline at end of file From ba112b46f5f98809274a33726dc06a8be86cde18 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 12:55:53 +0100 Subject: [PATCH 22/25] apparently there is a bug in the trusty opus library re. mono encoding --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7eeaeb7..985df66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,13 @@ -sudo: false +sudo: required +dist: xenial language: python addons: apt: packages: - - gstreamer1.0-plugins-base # Latest gstreamer has Opus in base - - gstreamer1.0-plugins-good # RTP/UDP/audio plugins reside in good - - gstreamer1.0-plugins-bad # The version of gstreamer in Trusty still has Opus in bad + - gstreamer1.0-plugins-base + - gstreamer1.0-plugins-good - gir1.2-gstreamer-1.0 - python-gst-1.0 - python-gobject From 67a7ff6a1c2671fbef78cb81a36f3b6fd7297fb9 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 13:00:08 +0100 Subject: [PATCH 23/25] Xenial has python 3.5 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 985df66..7b5d146 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ services: python: - 2.7 - - 3.4 + - 3.5 virtualenv: system_site_packages: true # Required as Travis runs python in a virtualenv, thus denying access to system GLib From 5f03751906ddb7964662a35d0abd5e42227e61d2 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 13:17:50 +0100 Subject: [PATCH 24/25] remove direct dependencies from setup.py --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 119cb76..9c00e50 100755 --- a/setup.py +++ b/setup.py @@ -9,8 +9,6 @@ url='http://jamesharrison.github.com/openob', scripts=['bin/openob'], packages=['openob', 'openob.rtp'], - requires=['pygst', 'redis'], - install_requires=['redis'], classifiers=["Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 2", From 74dc68ad527a66f309500f80b8fbcea802ee5017 Mon Sep 17 00:00:00 2001 From: Jonty Sewell Date: Tue, 10 Jul 2018 13:28:22 +0100 Subject: [PATCH 25/25] We don't need python-gobject after all, that is a deprecated package --- .travis.yml | 6 ++++-- doc/source/tutorial.rst | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b5d146..43357a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,11 @@ addons: - gstreamer1.0-plugins-base - gstreamer1.0-plugins-good - gir1.2-gstreamer-1.0 - - python-gst-1.0 - - python-gobject + - python-gst-1.0 + - python3-gst-1.0 - python-redis + - python3-redis + - python-gobject - python-gi services: diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 2c91427..9efcfcc 100755 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -21,8 +21,9 @@ On Debian Stretch / Ubuntu Xenial you can install the prerequisites with the fol .. code-block:: bash - sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good gir1.2-gstreamer-1.0 python-gst-1.0 python-gobject python-redis python-gi python-setuptools + sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good gir1.2-gstreamer-1.0 python-gst-1.0 python-redis python-gi python-setuptools +If you wish to use Python 3, you must install `python3-redis`, `python3-gst-1.0` and `python3-setuptools` instead of the Python 2 equivalents. The GStreamer Opus plugin has graduated from the 'bad' plugins repository to the 'base' repository as of 2015. Older distributions may require the `gstreamer1.0-plugins-bad` package installed. In order to ensure compatibility, it is recommended that both ends of the link use the same version of GStreamer, which is most easily achieved by running the same operating system version on each end and installing the distribution's packages as detailed above.