diff options
-rw-r--r-- | voctocore/README.md | 7 | ||||
-rw-r--r-- | voctocore/default-config.ini | 8 | ||||
-rw-r--r-- | voctocore/lib/audiomix.py | 10 | ||||
-rw-r--r-- | voctocore/lib/avpreviewoutput.py | 96 | ||||
-rw-r--r-- | voctocore/lib/avsource.py | 22 | ||||
-rw-r--r-- | voctocore/lib/pipeline.py | 24 | ||||
-rw-r--r-- | voctocore/lib/videomix.py | 14 |
7 files changed, 166 insertions, 15 deletions
diff --git a/voctocore/README.md b/voctocore/README.md index 55d19e3..8503819 100644 --- a/voctocore/README.md +++ b/voctocore/README.md @@ -2,15 +2,16 @@ ```` /-> VideoMix / \ - / \ /-> StreamBlanker -> StreamOutputPort 11001 + / \ /-> StreamBlanker -> StreamOutputPort 15000 / ------> OutputPort 11000 - / / \-> Encoder -> PreviewPort 14000… + / / \-> Encoder* -> PreviewPort* 12000 / / /----- -> AudioMix / 10000… AVSource --> MirrorPort 13000… - \-> Encoder -> PreviewPort 14000… + \-> Encoder* -> PreviewPort* 14000… +*) only when encode_previews=true is configured ```` # Control Protocol diff --git a/voctocore/default-config.ini b/voctocore/default-config.ini index 517afa6..b7751f0 100644 --- a/voctocore/default-config.ini +++ b/voctocore/default-config.ini @@ -2,12 +2,14 @@ videocaps=video/x-raw,format=I420,width=1280,height=720,framerate=25/1,pixel-aspect-ratio=1/1 audiocaps=audio/x-raw,format=S16LE,channels=2,layout=interleaved,rate=48000,channel-mask=(bitmask)0x3 -; disable if ui & server run on the same computer and excahnge uncompressed video frames -encode_previews=true - ; tcp-ports will be 10000,10001 sources=cam1,cam2,grabber +[previews] +; disable if ui & server run on the same computer and exchange uncompressed video frames +enabled=true +videocaps=video/x-raw,width=1024,height=576,framerate=25/1 + [pause] ;image=/video/pause.png video=/video/pause.m4v diff --git a/voctocore/lib/audiomix.py b/voctocore/lib/audiomix.py index e307527..1be32d4 100644 --- a/voctocore/lib/audiomix.py +++ b/voctocore/lib/audiomix.py @@ -24,11 +24,19 @@ class AudioMix(object): pipeline = """ audiomixer name=mix ! {caps} ! - interaudiosink channel=audio_mix + queue ! + tee name=tee + + tee. ! queue ! interaudiosink channel=audio_mix_out """.format( caps=self.caps ) + if Config.getboolean('previews', 'enabled'): + pipeline += """ + tee. ! queue ! interaudiosink channel=audio_mix_preview + """ + for idx, name in enumerate(self.names): pipeline += """ interaudiosrc channel=audio_{name}_mixer ! diff --git a/voctocore/lib/avpreviewoutput.py b/voctocore/lib/avpreviewoutput.py new file mode 100644 index 0000000..2fa8c3a --- /dev/null +++ b/voctocore/lib/avpreviewoutput.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 +import logging, socket +from gi.repository import GObject, Gst + +from lib.config import Config + +class AVPreviewOutput(object): + log = logging.getLogger('AVPreviewOutput') + + name = None + port = None + caps = None + + boundSocket = None + receiverPipeline = None + + currentConnections = [] + + def __init__(self, channel, port): + self.log = logging.getLogger('AVPreviewOutput['+channel+']') + + self.channel = channel + self.port = port + + if Config.has_option('previews', 'videocaps'): + vcaps_out = Config.get('previews', 'videocaps') + else: + vcaps_out = Config.get('mix', 'videocaps') + + pipeline = """ + interaudiosrc channel=audio_{channel} ! + {acaps} ! + queue ! + mux. + + intervideosrc channel=video_{channel} ! + {vcaps_in} ! + textoverlay halignment=left valignment=top ypad=75 text=AVPreviewOutput ! + timeoverlay halignment=left valignment=top ypad=75 xpad=400 ! + videorate ! + videoscale ! + {vcaps_out} ! + jpegenc ! + queue ! + mux. + + matroskamux + name=mux + streamable=true + writing-app=Voctomix-AVPreviewOutput ! + + multifdsink + sync-method=next-keyframe + name=fd + """.format( + channel=self.channel, + acaps=Config.get('mix', 'audiocaps'), + vcaps_in=Config.get('mix', 'videocaps'), + vcaps_out=vcaps_out + ) + + self.log.debug('Launching Output-Pipeline:\n%s', pipeline) + self.receiverPipeline = Gst.parse_launch(pipeline) + self.receiverPipeline.set_state(Gst.State.PLAYING) + + self.log.debug('Binding to Output-Socket on [::]:%u', port) + self.boundSocket = socket.socket(socket.AF_INET6) + self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) + self.boundSocket.bind(('::', port)) + self.boundSocket.listen(1) + + self.log.debug('Setting GObject io-watch on Socket') + GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect) + + def on_connect(self, sock, *args): + conn, addr = sock.accept() + self.log.info("Incomming Connection from %s", addr) + + def on_disconnect(multifdsink, fileno): + if fileno == conn.fileno(): + self.log.debug('fd %u removed from multifdsink', fileno) + + self.currentConnections.remove(conn) + self.log.info('Disconnected Receiver %s', addr) + self.log.info('Now %u Receiver connected', len(self.currentConnections)) + + self.log.debug('Adding fd %u to multifdsink', conn.fileno()) + fdsink = self.receiverPipeline.get_by_name('fd') + fdsink.emit('add', conn.fileno()) + fdsink.connect('client-fd-removed', on_disconnect) + + self.currentConnections.append(conn) + self.log.info('Now %u Receiver connected', len(self.currentConnections)) + + return True diff --git a/voctocore/lib/avsource.py b/voctocore/lib/avsource.py index 49b39a8..a809948 100644 --- a/voctocore/lib/avsource.py +++ b/voctocore/lib/avsource.py @@ -51,7 +51,20 @@ class AVSource(object): atee. ! queue ! interaudiosink channel=audio_{name}_mixer atee. ! queue ! interaudiosink channel=audio_{name}_mirror + """.format( + fd=conn.fileno(), + name=self.name, + acaps=Config.get('mix', 'audiocaps') + ) + if Config.getboolean('previews', 'enabled'): + pipeline += """ + atee. ! queue ! interaudiosink channel=audio_{name}_preview + """.format( + name=self.name + ) + + pipeline += """ demux. ! {vcaps} ! textoverlay halignment=left valignment=top ypad=25 text=AVSource ! @@ -64,9 +77,16 @@ class AVSource(object): """.format( fd=conn.fileno(), name=self.name, - acaps=Config.get('mix', 'audiocaps'), vcaps=Config.get('mix', 'videocaps') ) + + if Config.getboolean('previews', 'enabled'): + pipeline += """ + vtee. ! queue ! intervideosink channel=video_{name}_preview + """.format( + name=self.name + ) + self.log.debug('Launching Source-Pipeline:\n%s', pipeline) self.receiverPipeline = Gst.parse_launch(pipeline) diff --git a/voctocore/lib/pipeline.py b/voctocore/lib/pipeline.py index 3aa8085..5a9ce3e 100644 --- a/voctocore/lib/pipeline.py +++ b/voctocore/lib/pipeline.py @@ -6,6 +6,7 @@ from gi.repository import Gst from lib.config import Config from lib.avsource import AVSource from lib.avrawoutput import AVRawOutput +from lib.avpreviewoutput import AVPreviewOutput from lib.videomix import VideoMix from lib.audiomix import AudioMix @@ -14,9 +15,12 @@ class Pipeline(object): log = logging.getLogger('Pipeline') sources = [] - outputs = [] + mirrors = [] + previews = [] + vmix = None amix = None + mixout = None def __init__(self): self.log.info('Video-Caps configured to: %s', Config.get('mix', 'videocaps')) @@ -39,7 +43,15 @@ class Pipeline(object): self.log.info('Creating Mirror-Output for AVSource %s at tcp-port %u', name, port) mirror = AVRawOutput('%s_mirror' % name, port) - self.outputs.append(mirror) + self.mirrors.append(mirror) + + + if Config.getboolean('previews', 'enabled'): + port = 14000 + idx + self.log.info('Creating Preview-Output for AVSource %s at tcp-port %u', name, port) + + preview = AVPreviewOutput('%s_preview' % name, port) + self.previews.append(preview) self.log.info('Creating Videmixer') @@ -50,6 +62,10 @@ class Pipeline(object): port = 11000 self.log.info('Creating Mixer-Output at tcp-port %u', port) + self.mixout = AVRawOutput('mix_out', port) + + if Config.getboolean('previews', 'enabled'): + port = 12000 + self.log.info('Creating Preview-Output for AVSource %s at tcp-port %u', name, port) - output = AVRawOutput('mix', port) - self.outputs.append(output) + self.mixpreview = AVPreviewOutput('mix_preview', port) diff --git a/voctocore/lib/videomix.py b/voctocore/lib/videomix.py index 77bea42..147b1ba 100644 --- a/voctocore/lib/videomix.py +++ b/voctocore/lib/videomix.py @@ -30,13 +30,21 @@ class VideoMix(object): pipeline = """ videomixer name=mix ! {caps} ! - textoverlay halignment=left valignment=top ypad=175 text=VideoMix ! - timeoverlay halignment=left valignment=top ypad=175 xpad=400 ! - intervideosink channel=video_mix + textoverlay halignment=left valignment=top ypad=125 text=VideoMix ! + timeoverlay halignment=left valignment=top ypad=125 xpad=400 ! + queue ! + tee name=tee + + tee. ! queue ! intervideosink channel=video_mix_out """.format( caps=self.caps ) + if Config.getboolean('previews', 'enabled'): + pipeline += """ + tee. ! queue ! intervideosink channel=video_mix_preview + """ + for idx, name in enumerate(self.names): pipeline += """ intervideosrc channel=video_{name}_mixer ! |