Compare commits

...

9 Commits
master ... gstd

16 changed files with 488 additions and 19 deletions

View File

@ -6,12 +6,12 @@ ENV SNOWMIX_VERSION 0.5.1
WORKDIR /opt
RUN apt update && apt -y --no-install-recommends install gstreamer1.0-tools build-essential automake \
autoconf libtool g++ pkg-config libsdl1.2-dev libpango1.0-dev \
libpng-dev libosmesa6-dev freeglut3-dev wget ca-certificates
libpng-dev libosmesa6-dev freeglut3-dev wget ca-certificates \
\
tcl tk bwidget tcl-dev
RUN wget https://downloads.sourceforge.net/project/snowmix/Snowmix-${SNOWMIX_VERSION}.tar.gz -O Snowmix.tgz && \
tar xvf Snowmix.tgz
RUN apt -y --no-install-recommends install tcl tk bwidget tcl-dev
RUN cd Snowmix-${SNOWMIX_VERSION} && \
aclocal && autoconf && libtoolize --force && automake --add-missing && \
./configure && make && make install
@ -20,30 +20,40 @@ ENV SNOWMIX /usr/local/lib/Snowmix-${SNOWMIX_VERSION}
RUN useradd snowmix && mkdir /home/snowmix /run/snowmix && chown snowmix /home/snowmix /run/snowmix
WORKDIR /config
RUN apt install -y --no-install-recommends \
RUN apt update && apt install -y --no-install-recommends \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-plugins-base gstreamer1.0-x gstreamer1.0-pulseaudio \
netcat bc golang git libpcap-dev
gstreamer1.0-plugins-base gstreamer1.0-x gstreamer1.0-pulseaudio gstreamer1.0-libav gstreamer1.0-vaapi \
netcat bc golang git libpcap-dev \
\
python-pyparsing python-gst-1.0 libgstreamer-plugins-base1.0-dev libglib2.0-dev \
libjson-glib-dev libreadline-dev libncursesw5-dev libdaemon-dev libjansson-dev gtk-doc-tools \
\
libgstreamer1.0-dev python-gi-dev python-dev
# Patch netcat!
RUN sed -i -e 's_NC\=.*_NC="nc -q1"_' /usr/local/lib/Snowmix-0.5.1/scripts/snowmix-settings
RUN apt install -y --no-install-recommends libgstreamer1.0-dev python-gi-dev python-dev
RUN git clone git://anongit.freedesktop.org/git/gstreamer/gst-python -b 1.14.1 /opt/gst-python && \
cd /opt/gst-python && \
./autogen.sh --disable-gtk-doc --noconfigure && \
./configure --with-libpython-dir="/usr/lib/x86_64-linux-gnu" && \
make && \
make install
make install && rm -rf /opt/gst-python
RUN apt install -y --no-install-recommends python-pyparsing python-gst-1.0
RUN git clone https://github.com/RidgeRun/gstd-1.x.git /opt/gstd && cd /opt/gstd && ./autogen.sh && ./configure && make && make install && rm -rf /opt/gstd
RUN git clone https://github.com/RidgeRun/gst-interpipe.git /opt/gst-interpipe && cd /opt/gst-interpipe && ./autogen.sh --noconfigure && ./configure --libdir /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ --disable-gtk-doc && make && make install && rm -rf /opt/gst-interpipe
ADD ./tools /tools
ADD ./gst-snowmix /opt/gst-snowmix
RUN rm /opt/gst-snowmix/python/snowmix.py
ADD ./api/snowmix.py /opt/gst-snowmix/python
ENV GST_PLUGIN_PATH $GST_PLUGIN_PATH:/usr/local/lib/gstreamer-1.0:/opt/gst-snowmix
ENV GOPATH /usr/src/go
RUN mkdir $GOPATH && cd /tools/de-ip-hdmi && go get -d . && go build . && chmod +s /tools/de-ip-hdmi/de-ip-hdmi
ENV GST_PLUGIN_PATH $GST_PLUGIN_PATH:/opt/gst-snowmix
RUN mkdir $GOPATH && cd /tools/de-ip-hdmi && go get -d . && go build . && chmod +s /tools/de-ip-hdmi/de-ip-hdmi && rm -rf $GOPATH
RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/54d1f0bfeb6557adf8a3204455389d0901652242/wait-for-it.sh -O /usr/local/bin/wait-for-it && chmod +x /usr/local/bin/wait-for-it
USER snowmix
WORKDIR /config
CMD [ "/tools/run-snowmix" ]

View File

@ -2,6 +2,7 @@ import socket
import re
import logging
import threading
import time
from pyparsing import nestedExpr, originalTextFor
@ -66,7 +67,7 @@ class SnowmixClient(object):
self.fd.write(command + '\r\n')
self.fd.flush()
while True:
while expect:
line = self.fd.readline()
if not line:
@ -201,6 +202,27 @@ class SnowmixClient(object):
for line in self.call('audio %s channels' % (audio_type), 'STAT:')
}
def audio_volume(self, audio_id, volume_l, volume_r=None, audio_type='feed'):
if volume_r is None:
volume_r = volume_l
return next(self.call('audio %s volume %s %f %f' % (audio_type, audio_id, volume_l, volume_r), None), None)
def audio_mute(self, audio_id, mute, audio_type='feed'):
mute = 'on' if mute else 'off'
return next(self.call('audio %s mute %s %s' % (audio_type, mute, audio_id), None), None)
def audio_status(self, audio_type='feed'):
return dict(parse_table(list((self.call('audio %s status' % (audio_type,), 'STAT:')))))
def parse_table(lines):
keys = lines[0].split(' : ')[1].split(' ')
for line in lines[1:]:
id_, _, values = line.partition(' : ')
yield (id_.split(' ')[-1], dict(zip(keys, values.split(' '))))
#return [dict(zip(keys, l.split(' : ')[1].split(' '))) for l in lines[1:]]
if __name__ == "__main__":
c = SnowmixClient('snowmix')

View File

@ -88,22 +88,32 @@ tcl eval SceneSetBackground 0 1
tcl eval SceneCreate "Fullscreen 1" 1
#tcl eval SceneAddFrame 1 1 0 0 1280 720
tcl eval SceneAddFrame 1 2 1410 20 480 270
tcl eval SceneSetFrameSource 1 1 feed 2 0 1
tcl eval SceneSetFrameSource 1 1 feed 1 1 1
tcl eval SceneSetFrameSource 1 1 feed 2 0 1
tcl eval SceneSetFrameSource 1 2 feed 2 1 1
tcl eval SceneSetFrameActive 1 2 0 1
tcl eval SceneSetBackground 1 1
# Disable background on fullscreen scenes
tcl eval SceneAlphaLink 1 -2 0
tcl eval SceneAlpha 1 -2 0
# Scene 2
tcl eval SceneCreate "Fullscreen 2" 2
tcl eval SceneAddFrame 2 1 0 0 1920 1080
tcl eval SceneAddFrame 2 2 1410 20 480 270
tcl eval SceneSetFrameSource 2 1 feed 2 0 1
tcl eval SceneSetFrameSource 2 1 feed 1 1 1
tcl eval SceneSetFrameSource 2 2 feed 2 1 1
tcl eval SceneSetFrameSource 2 1 feed 2 1 1
tcl eval SceneSetFrameSource 2 2 feed 1 1 1
tcl eval SceneSetBackground 2 1
# Disable background on fullscreen scenes
tcl eval SceneAlphaLink 2 -2 0
tcl eval SceneAlpha 2 -2 0
tcl eval SceneSetState 0 1
# Clean fade between scenes (no FTB)
tcl eval SceneFadeSpeed pause all - 0
include ini/streaming-audio
stack 0

View File

@ -6,8 +6,17 @@ x-defaults: &defaults
working_dir: /tools
volumes:
- sockets:/run/snowmix
- ./tools:/tools:ro
- ./config:/config:ro
- ./assets:/assets:ro
- /storage:/storage
- /tmp/.X11-unix:/tmp/.X11-unix
environment:
- DISPLAY=${DISPLAY:-:0}
network_mode: host
ipc: host
extra_hosts:
- "janus:172.17.0.1"
services:
snowmix:
@ -34,12 +43,22 @@ services:
- 8005:8005/udp
network_mode: host
gstd:
<<: *defaults
command: gstd -D --gst-debug-level=4
gstd-init:
<<: *defaults
command: wait-for-it localhost:5000 -- gstd-client source init.gstd
restart: "no"
janus-feed:
build: .
restart: unless-stopped
volumes:
- sockets:/run/snowmix
- ./tools:/tools:ro
- ./gst-snowmix:/opt/gst-snowmix
- /storage:/storage
environment:
- SNOWMIX_YOUTUBE_SECRET
@ -63,7 +82,7 @@ services:
build: api
restart: unless-stopped
ports:
- 5000:5000
- 5001:5000
volumes:
- ./api:/app

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
import gi
gi.require_version('GstBase', '1.0')
from gi.repository import Gst, GObject, GstBase
Gst.init(None)
class SnowmixAudioSrc(Gst.Pipeline):
__gstmetadata__ = ('testbin Python','Transform', \
'Simple testbin element written in python', 'Marianna S. Buschle')
__gsttemplates__ = (Gst.PadTemplate.new("src",
Gst.PadDirection.SRC,
Gst.PadPresence.ALWAYS,
Gst.Caps.new_any()))
# Doesn't work either...
#ip = GObject.Property(type=str, default='127.0.0.1')
#port = GObject.Property(type=int, default=9999)
__gproperties__ = {
"window-duration": (float,
"Window Duration",
"Duration of the sliding window, in seconds",
0.01,
100.0,
5,
GObject.ParamFlags.READWRITE
),
"test-window-duration": (float,
"Window Duration",
"Duration of the sliding window, in seconds",
0.01,
100.0,
5,
GObject.ParamFlags.READWRITE
)
}
def __init__(self):
Gst.Bin.__init__(self)
Gst.info('hello xD')
#print(self.ip)
#print(self.port)
testsrc = Gst.ElementFactory.make("audiotestsrc", "audio_source")
self.add(testsrc)
self.add_pad(Gst.GhostPad.new("src", list(testsrc.iterate_src_pads())[0]))
self.window_duration = 5
def do_get_property(self, prop):
print('get',prop.name)
if prop.name.endswith('window-duration'):
return self.window_duration
else:
raise AttributeError('unknown property %s' % prop.name)
def do_set_property(self, prop, value):
print('set',prop.name)
if prop.name == 'window-duration':
self.window_duration = value
else:
raise AttributeError('unknown property %s' % prop.name)
GObject.type_register(SnowmixAudioSrc)
__gstelementfactory__ = ("snowmixaudiosrc", Gst.Rank.NONE, SnowmixAudioSrc)

107
gstd-gui/gui.py Normal file
View File

@ -0,0 +1,107 @@
import socket
import json
import re
from flask import Flask, render_template, redirect, flash, request, url_for
class GSTDException(Exception):
pass
class GSTDClient(object):
def __init__(self, addr='127.0.0.1', port=5000):
self.addr = addr
self.port = port
def connect(self):
self.sock = socket.socket()
self.sock.connect((self.addr, self.port))
def request(self, cmd, args=None):
if args:
cmd += ' ' + args
print('<', cmd)
self.sock.send(cmd.encode())
resp = self.recv_object()
print('>', resp)
if resp['code'] != 0:
raise GSTDException(resp)
return resp['response']
def recv_object(self):
buf = ''
decoder = json.JSONDecoder()
while True:
buf += self.sock.recv(4096).decode().replace('],{', '],') # wat
try:
obj, end = decoder.raw_decode(buf, 0)
return obj
except ValueError as exc:
print(exc)
pass
def pipelines_state(self):
return {
p['name']: self.request('read', '/pipelines/%s/state' % (p['name'],))['value']
for p in self.request('list_pipelines')['nodes']
}
def pipeline_elements(self, name):
elements = self.request('read', '/pipelines/%s/elements' % (name,))['nodes']
return {
e['name']: self.request('read', '/pipelines/%s/elements/%s' % (name, e['name']))['element_properties']
for e in elements
}
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['SECRET_KEY'] = 'changeme'
def parse_access(access):
return int(re.findall('\d+', access)[0])
@app.template_filter()
def access_writable(access):
return bool(parse_access(access) & 2)
def gstd():
c = GSTDClient()
c.connect()
return c
@app.route('/')
def index():
c = gstd()
return render_template('index.html', pipelines=c.pipelines_state())
@app.route('/pipelines/<name>/edit')
def pipeline_details(name):
c = gstd()
return render_template('pipeline_details.html',
elements=c.pipeline_elements(name), pipeline=name)
@app.route('/pipelines/<pipeline>/edit/<element>/<prop>')
def element_edit(pipeline, element, prop):
c = gstd()
try:
c.request('element_set', ' '.join([pipeline, element, prop, request.args.get('value', '')]))
except GSTDException as exc:
flash(exc.args[0]['description'], 'danger')
return redirect(url_for('pipeline_details', name=pipeline))
@app.route('/pipelines/<name>/<action>')
def pipeline_action(name, action):
c = gstd()
try:
c.request('pipeline_%s' % action, name)
except GSTDException as exc:
flash(exc.args[0]['description'], 'danger')
return redirect('/')

View File

@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Starter Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic-bootstrap.css" rel="stylesheet">
</head>
<body>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<table class="table table-hover table-striped">
<thead><tr>
<th>Name</th>
<th>State</th>
<th>Actions</th>
</tr></thead>
{% for pipeline, state in pipelines.items() %}
<tr>
<td><code>{{ pipeline }}</code></td>
{% set state_colors = {'PLAYING': 'success', 'PAUSED': 'warning', 'NULL': 'danger'} %}
<td><span class="badge badge-{{ state_colors[state] }}">{{ state }}</span></td>
<td>
<div class="btn-group">
{% if state == 'PLAYING' %}
<a href="{{ url_for('pipeline_action', name=pipeline, action='pause') }}" class="btn btn-sm btn-warning"><span class="oi oi-media-pause" title="pause" aria-hidden="true"></span></a>
{% else %}
<a href="{{ url_for('pipeline_action', name=pipeline, action='play') }}" class="btn btn-sm btn-success"><span class="oi oi-media-play" title="play" aria-hidden="true"></span></a>
{% endif %}
<a href="{{ url_for('pipeline_action', name=pipeline, action='stop') }}" class="btn btn-sm btn-danger"><span class="oi oi-media-stop" title="stop" aria-hidden="true"></span></a>
<a href="{{ url_for('pipeline_action', name=pipeline, action='edit') }}" class="btn btn-sm btn-info"><span class="oi oi-pencil" title="edit" aria-hidden="true"></span></a>
<a href="{{ url_for('pipeline_action', name=pipeline, action='delete') }}" class="btn btn-sm btn-danger"><span class="oi oi-x" title="delete" aria-hidden="true"></span></a>
</div>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<table>
{% for element, properties in elements.items() %}
<tr><th colspan=3>
{{ element }}
</th></tr>
{% for prop in properties %}
<tr>
<td>{{ prop.name }}</td>
<td>
{% if prop.param.access|access_writable %}
<form action="{{ url_for('element_edit', pipeline=pipeline, element=element, prop=prop.name) }}">
<div class="input-group">
<input type="text" name="value" value="{{ prop.value }}" class="form-control" />
<div class="input-group-append"><button class="btn btn-outline-secondary">Save</button></div>
</div>
</form>
{% else %}
{{ prop.value }}
{% endif %}
<small>{{ prop.param.description }}</small>
</td>
</tr>
{% endfor %}
{% endfor %}
</table>
{% endblock %}

84
snowmix-osc/server.py Normal file
View File

@ -0,0 +1,84 @@
"""Small example OSC server
This program listens to several addresses, and prints some information about
received packets.
"""
import argparse
import math
import threading
import time
from pythonosc import dispatcher
from pythonosc import osc_server
from pythonosc import udp_client
from snowmix import SnowmixClient
def snowmix_wrap(func, **kwargs):
def wrapped(addr, *args):
print(func, addr, *args)
return func(*args)
return wrapped
class PollerThread(threading.Thread):
daemon = True
def run(self):
while True:
try:
for scene_id, state in self.client.tcl('SceneSetState'):
print(scene_id, state)
self.osc.send_message('/scene/state', [scene_id, float(state)])
for feed_id, status in self.client.audio_status().items():
#print(status)
self.osc.send_message('/feed/state', [feed_id, status['state']])
for i, v in enumerate(status['rms'].split(',')):
#msg = '/feed/%s/rms_%s' % (feed_id, ['l', 'r'][i])
#print(msg, float(v))
#self.osc.send_message(msg, float(v))
msg = '/feed/rms_%s' % (['l', 'r'][i],)
self.osc.send_message(msg, [feed_id, float(v)])
#print(list(self.client.call('audio feed status', 'STAT:'))[1:])
#for s in self.client.scene_list():
# print(self.client.scene_info(s))
except Exception as exc:
print(exc)
pass
time.sleep(0.1)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ip",
default="127.0.0.1", help="The ip to listen on")
parser.add_argument("--port",
type=int, default=5005, help="The port to listen on")
parser.add_argument("--status-ip",
default="255.255.255.255", help="IP to report status on")
parser.add_argument("--status-port",
type=int, default=8080, help="The port to report status on")
parser.add_argument("--snowmix-ip",
default="10.8.0.95", help="Snowmix server IP")
parser.add_argument("--snowmix-port", type=int, default=9999, help="Snowmix port")
args = parser.parse_args()
s = SnowmixClient(args.snowmix_ip, args.snowmix_port)
dispatcher = dispatcher.Dispatcher()
#dispatcher.map("/filter", print)
dispatcher.map("/feed/volume", snowmix_wrap(s.audio_volume))
dispatcher.map("/feed/mute", snowmix_wrap(s.audio_mute))
dispatcher.map("/scene/fade", snowmix_wrap(s.scene_fade))
dispatcher.map("/scene/cut", snowmix_wrap(s.scene_cut))
poller = PollerThread()
poller.osc = udp_client.SimpleUDPClient(args.status_ip, args.status_port, True)
poller.client = s
poller.start()
server = osc_server.ThreadingOSCUDPServer(
(args.ip, args.port), dispatcher)
print("Serving on {}".format(server.server_address))
server.serve_forever()

1
snowmix-osc/snowmix.py Symbolic link
View File

@ -0,0 +1 @@
../api/snowmix.py

5
tools/init.gstd Normal file
View File

@ -0,0 +1,5 @@
#source preview.gstd
source snowmix.gstd
source mkv.gstd
pipeline_play shortpipe

37
tools/mkv.gstd Normal file
View File

@ -0,0 +1,37 @@
pipeline_delete mp3_enc
pipeline_create mp3_enc interpipesrc listen-to=audio_out is-live=true enable-sync=true format=3 ! queue ! audioconvert ! audioresample ! audiorate ! queue ! lamemp3enc bitrate=128 ! interpipesink name=mp3_enc forward-events=true
# ! matroskamux streamable=true ! tcpserversink port=2138 host=0.0.0.0
#pipeline_play mp3_enc
pipeline_delete h264_enc
pipeline_create h264_enc interpipesrc format=3 listen-to=video_out is-live=true enable-sync=true ! queue ! videoconvert ! video/x-raw,format=I420 ! queue ! x264enc bitrate=5000 key-int-max=30 bframes=0 byte-stream=false aud=true tune=zerolatency speed-preset=ultrafast ! h264parse ! interpipesink name=h264_out forward-events=true
#pipeline_play h264_enc
pipeline_delete h264_hi_enc
pipeline_create h264_hi_enc interpipesrc format=3 listen-to=video_out is-live=true enable-sync=true ! queue ! videoconvert ! queue ! x264enc bitrate=15000 key-int-max=30 bframes=0 byte-stream=false aud=true tune=zerolatency speed-preset=ultrafast ! h264parse ! interpipesink name=h264_hi_out forward-events=true
pipeline_delete flv
pipeline_create flv interpipesrc format=3 listen-to=h264_out is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! flvmux streamable=true name=flvmux ! queue ! tcpserversink host=0.0.0.0 port=2137 sync=false async=false interpipesrc listen-to=mp3_enc is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! mpegaudioparse ! audio/mpeg,rate=44100,channels=2 ! queue ! flvmux.
pipeline_delete flv_record
pipeline_create flv_record interpipesrc format=3 listen-to=h264_hi_out is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! flvmux streamable=true name=flvmux ! queue ! filesink location=/tmp/recording.mkv sync=false async=false interpipesrc listen-to=mp3_enc is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! mpegaudioparse ! audio/mpeg,rate=44100,channels=2 ! queue ! flvmux.
pipeline_delete mkv
pipeline_create mkv interpipesrc format=3 listen-to=h264_out is-live=true allow-renegotiation=true enable-sync=true accept-events=true ! h264parse ! queue ! matroskamux streamable=true name=mkvmux ! queue ! tcpserversink host=0.0.0.0 port=2138 sync=false async=false interpipesrc enable-sync=false listen-to=mp3_enc is-live=true allow-renegotiation=true accept-events=true ! queue ! mpegaudioparse ! audio/mpeg,rate=44100,channels=2 ! queue ! mkvmux.
pipeline_delete mkv_record
pipeline_create mkv_record interpipesrc format=3 listen-to=h264_hi_out is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! matroskamux streamable=true name=mkvmux ! queue ! filesink location=/tmp/recording.mkv sync=false async=false interpipesrc listen-to=mp3_enc is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! mpegaudioparse ! audio/mpeg,rate=44100,channels=2 ! queue ! mkvmux.
pipeline_delete shortpipe
#pipeline_create shortpipe interpipesrc format=3 listen-to=video_out is-live=true do-timestamp=true ! queue ! videoconvert ! queue ! x264enc bitrate=5000 key-int-max=30 bframes=0 byte-stream=false aud=true tune=zerolatency speed-preset=ultrafast threads=2 ! video/x-h264,profile=high ! tee name=encvid ! queue ! matroskamux streamable=true name=flvmux ! queue ! tcpserversink host=0.0.0.0 port=2137 sync=false async=false interpipesrc listen-to=audio_out is-live=true format=3 ! queue ! audioconvert ! audioresample ! audiorate ! queue ! lamemp3enc bitrate=128 ! queue ! mpegaudioparse ! queue ! audio/mpeg,rate=44100,channels=2 ! queue ! mpegaudioparse ! flvmux. encvid. ! queue ! rtph264pay config-interval=1 pt=96 ! udpsink host=janus port=8004
pipeline_delete interpipeshort
#pipeline_create interpipeshort interpipesrc listen-to=audio_out is-live=true enable-sync=true format=3 ! queue ! audioconvert ! audioresample ! audiorate ! queue ! lamemp3enc bitrate=128 ! interpipesink name=mp3_enc forward-events=true interpipesrc format=3 listen-to=h264_out is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! flvmux streamable=true name=flvmux ! queue ! tcpserversink host=0.0.0.0 port=2137 sync=false async=false interpipesrc listen-to=mp3_enc is-live=true enable-sync=true allow-renegotiation=true accept-events=true ! queue ! mpegaudioparse ! audio/mpeg,rate=44100,channels=2 ! queue ! mkvmux.
#pipeline_play interpipeshort
pipeline_play mkv
pipeline_play flv
pipeline_play h264_enc
pipeline_play mp3_enc

3
tools/playtest.gstd Normal file
View File

@ -0,0 +1,3 @@
pipeline_delete playback
pipeline_create playback playbin uri=file:///assets/Sync-Footage-V1-H264.mp4 audio-sink=snowmixaudiosink video-sink="videoconvert ! videorate ! queue ! video/x-raw,format=BGRA,pixel-aspect-ratio=1/1,interlace-mode=progressive,width=1920,height=1080,framerate=30/1 ! queue ! shmsink socket-path=/run/snowmix/feed1-control-pipe shm-size=165888000 wait-for-connection=0 sync=true"
pipeline_play playback

3
tools/preview.gstd Normal file
View File

@ -0,0 +1,3 @@
pipeline_delete preview
pipeline_create preview interpipesrc listen-to=video_out is_live=true ! videoconvert ! timeoverlay ! queue ! taginject tags="title=screen:DisplayPort-1:snowmix" ! ximagesink
pipeline_play preview

10
tools/snowmix.gstd Normal file
View File

@ -0,0 +1,10 @@
pipeline_delete video_out
pipeline_create video_out shmsrc socket-path=/run/snowmix/mixer1 do-timestamp=true is-live=true ! video/x-raw,format=(string)BGRA,pixel-aspect-ratio=(fraction)1/1,interlace-mode=(string)progressive,framerate=30/1,width=1920,height=1080 ! queue ! interpipesink name=video_out forward-events=true
pipeline_play video_out
pipeline_delete audio_out
pipeline_create audio_out snowmixaudiosrc ! queue ! interpipesink name=audio_out forward-events=true
pipeline_play audio_out
pipeline_delete video_mirror
pipeline_create video_mirror interpipesrc format=3 listen-to=video_out is-live=true enable-sync=true ! queue ! shmsink socket-path=/run/snowmix/mixer1-mirror wait-for-connection=false shm-size=165888000 sync=false async=false