mirror of
https://github.com/albfan/miraclecast.git
synced 2025-03-09 23:38:56 +00:00
UIBC support
Basic support for UIBC (single mouse events and key events) - Option --uibc on miracle-sinkctl to enable it - Option --log-journal-level to see player execution on journalctl - Controller for uibc miracle-uibcctl - Player based on PyGtk to receive and communicate mouse and keyevents closes #57
This commit is contained in:
parent
4637409d0b
commit
1e69dd6dae
21 changed files with 1258 additions and 79 deletions
|
@ -1,7 +1,7 @@
|
|||
|
||||
########### install files ###############
|
||||
|
||||
install(FILES miracle-gst.sh DESTINATION bin)
|
||||
install(FILES miracle-gst gstplayer DESTINATION bin)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
bin_SCRIPTS = miracle-gst.sh
|
||||
bin_SCRIPTS = miracle-gst gstplayer uibc-viewer
|
||||
EXTRA_DIST = wpa.conf
|
||||
|
|
219
res/gstplayer
Executable file
219
res/gstplayer
Executable file
|
@ -0,0 +1,219 @@
|
|||
#!/usr/bin/python3 -u
|
||||
|
||||
import gi
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
gi.require_version('Gst', '1.0')
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('GstVideo', '1.0')
|
||||
|
||||
from gi.repository import GObject, Gst, Gtk, Gdk, GLib
|
||||
|
||||
# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively:
|
||||
from gi.repository import GdkX11, GstVideo
|
||||
|
||||
GObject.threads_init()
|
||||
Gst.init(None)
|
||||
|
||||
class Player(object):
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
scale = kwargs.get("scale")
|
||||
#scale = "1080x1920"
|
||||
if not scale:
|
||||
self.width = 800
|
||||
self.height = 600
|
||||
else:
|
||||
split = scale.split("x")
|
||||
self.width = int(split[0])
|
||||
self.height = int(split[1])
|
||||
|
||||
port = kwargs.get("port")
|
||||
|
||||
uri = kwargs.get("uri")
|
||||
|
||||
self.window = Gtk.Window()
|
||||
self.window.set_name('eco')
|
||||
self.window.connect('destroy', self.quit)
|
||||
|
||||
title = kwargs.get("title")
|
||||
if title:
|
||||
self.window.set_title(title)
|
||||
|
||||
self.window.set_default_size(self.width, self.height)
|
||||
|
||||
self.drawingarea = Gtk.DrawingArea()
|
||||
self.window.add(self.drawingarea)
|
||||
|
||||
self.drawingarea.set_size_request(self.width,self.height)
|
||||
self.drawingarea.add_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.BUTTON_RELEASE_MASK)
|
||||
self.drawingarea.connect('button-press-event', self.on_mouse_pressed)
|
||||
self.drawingarea.connect('button-release-event', self.on_mouse_pressed)
|
||||
self.drawingarea.add_events(Gdk.EventMask.KEY_PRESS_MASK)
|
||||
self.window.connect('key-press-event', self.on_key_pressed)
|
||||
|
||||
audio = kwargs.get("audio")
|
||||
|
||||
self.playbin = None
|
||||
|
||||
#Create GStreamer pipeline
|
||||
if uri is not None:
|
||||
self.pipeline = Gst.Pipeline()
|
||||
|
||||
# Create GStreamer elements
|
||||
self.playbin = Gst.ElementFactory.make('playbin', "source")
|
||||
|
||||
if not uri.startswith("http://") or not uri.startswith("http://") or not uri.startswith("file://"):
|
||||
if not uri.startswith("/"):
|
||||
import os
|
||||
uri = os.path.abspath(uri)
|
||||
uri = "file://"+uri
|
||||
|
||||
# Set properties
|
||||
self.playbin.set_property('uri', uri)
|
||||
|
||||
|
||||
# Add playbin to the pipeline
|
||||
self.pipeline.add(self.playbin)
|
||||
else:
|
||||
gstcommand = "udpsrc port="+str(port)+" caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux "
|
||||
|
||||
if audio:
|
||||
gstcommand += "name=demuxer demuxer. "
|
||||
|
||||
gstcommand += "! queue max-size-buffers=0 max-size-time=0 ! h264parse ! avdec_h264 ! videoconvert ! "
|
||||
|
||||
if scale:
|
||||
gstcommand += "videoscale method=1 ! video/x-raw,width="+str(self.width)+",height="+str(self.height)+" ! "
|
||||
|
||||
gstcommand += "autovideosink "
|
||||
|
||||
if audio:
|
||||
gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink "
|
||||
|
||||
self.pipeline = Gst.parse_launch(gstcommand)
|
||||
|
||||
|
||||
# Create bus to get events from GStreamer pipeline
|
||||
self.bus = self.pipeline.get_bus()
|
||||
self.bus.add_signal_watch()
|
||||
self.bus.connect('message::eos', self.on_eos)
|
||||
self.bus.connect('message::error', self.on_error)
|
||||
|
||||
# This is needed to make the video output in our DrawingArea:
|
||||
self.bus.enable_sync_message_emission()
|
||||
self.bus.connect('sync-message::element', self.on_sync_message)
|
||||
self.bus.connect('message', self.on_message)
|
||||
|
||||
self.success = False
|
||||
|
||||
def on_message(self, bus, message):
|
||||
if self.playbin:
|
||||
videoPad = self.playbin.emit("get-video-pad", 0)
|
||||
if videoPad and not self.success:
|
||||
videoPadCapabilities = videoPad.get_current_caps()
|
||||
if videoPadCapabilities:
|
||||
(self.success, self.videoWidth) = \
|
||||
videoPadCapabilities.get_structure(0).get_int("width")
|
||||
(self.success, self.videoHeight) = \
|
||||
videoPadCapabilities.get_structure(0).get_int("height")
|
||||
if self.success:
|
||||
print("{0} {1}".format(self.videoWidth, self.videoHeight))
|
||||
self.drawingarea.set_size_request(self.videoWidth, self.videoHeight)
|
||||
|
||||
def on_mouse_pressed(self, widget, event):
|
||||
#<type>,<count>,<id>,<x>,<y>
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
type = 0
|
||||
else:
|
||||
type = 1
|
||||
|
||||
width = self.drawingarea.get_allocation().width
|
||||
height = self.drawingarea.get_allocation().height
|
||||
half_area_width = width / 2
|
||||
half_area_height = height / 2
|
||||
|
||||
half_def_width = self.width / 2
|
||||
half_def_height = self.height / 2
|
||||
|
||||
min_hor_pos = half_area_width - half_def_width
|
||||
max_hor_pos = half_area_width + half_def_width
|
||||
min_ver_pos = half_area_height - half_def_height
|
||||
max_ver_pos = half_area_height + half_def_height
|
||||
|
||||
pos_event_x = event.x
|
||||
pos_event_y = event.y
|
||||
|
||||
|
||||
#print('{0} {1} {2} {3} {4} {5}'.format(min_hor_pos, pos_event_x, max_hor_pos, min_ver_pos, pos_event_y,max_ver_pos))
|
||||
if min_hor_pos <= pos_event_x <= max_hor_pos and min_ver_pos <= pos_event_y <= max_ver_pos:
|
||||
uibc_x = int(pos_event_x - (half_area_width - half_def_width))
|
||||
#print ('{0} {1} {2} {3}'.format(uibc_x, pos_event_x, half_area_width,half_def_width))
|
||||
uibc_y = int(pos_event_y - (half_area_height - half_def_height))
|
||||
#print ('{0} {1} {2} {3}'.format(uibc_y, pos_event_y, half_area_height,half_def_height))
|
||||
print('{0},1,0,{1},{2}'.format(type, uibc_x , uibc_y))
|
||||
|
||||
def on_key_pressed(self, widget, event):
|
||||
#print(Gdk.keyval_name(event.keyval))
|
||||
print("3,0x%04X,0x0000" % event.keyval)
|
||||
# print(event.state)
|
||||
# if event.state & Gdk.ModifierType.LOCK_MASK == Gdk.ModifierType.LOCK_MASK:
|
||||
# print("caps lock")
|
||||
# print(Gtk.accelerator_get_label(event.keyval, event.state))
|
||||
|
||||
def run(self):
|
||||
self.window.show_all()
|
||||
# You need to get the XID after window.show_all(). You shouldn't get it
|
||||
# in the on_sync_message() handler because threading issues will cause
|
||||
# segfaults there.
|
||||
self.xid = self.drawingarea.get_property('window').get_xid()
|
||||
self.pipeline.set_state(Gst.State.PLAYING)
|
||||
Gtk.main()
|
||||
|
||||
|
||||
def quit(self, window):
|
||||
self.pipeline.set_state(Gst.State.NULL)
|
||||
Gtk.main_quit()
|
||||
|
||||
def on_sync_message(self, bus, msg):
|
||||
if msg.get_structure().get_name() == 'prepare-window-handle':
|
||||
print(self.drawingarea.get_allocation())
|
||||
#msg.src.set_property("force-aspect-radio", True)
|
||||
msg.src.set_window_handle(self.xid)
|
||||
|
||||
def on_eos(self, bus, msg):
|
||||
print('on_eos(): seeking to start of video')
|
||||
self.pipeline.seek_simple(
|
||||
Gst.Format.TIME,
|
||||
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
|
||||
0
|
||||
)
|
||||
|
||||
def on_error(self, bus, msg):
|
||||
print('on_error():', msg.parse_error())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("uri", nargs="?", help="Uri to play")
|
||||
parser.add_argument("-v", "--version", help="Show package version")
|
||||
parser.add_argument("--log-level", metavar="lvl", help="Maximum level for log messages")
|
||||
parser.add_argument("-p", "--port", type=int, default=1991, help="Port for rtsp")
|
||||
parser.add_argument("-a", "--audio", dest="audio", action="store_true", help="Enable audio support")
|
||||
parser.add_argument("-s", "--scale", metavar="WxH", help="Scale to resolution")
|
||||
parser.add_argument("-d", "--debug", help="Debug")
|
||||
parser.add_argument("--uibc", help="Enable UIBC")
|
||||
parser.add_argument("--title", help="set player title")
|
||||
parser.add_argument("--res", metavar="n,n,n", help="Supported resolutions masks (CEA, VESA, HH)")
|
||||
# res
|
||||
# " default CEA %08X\n"
|
||||
# " default VESA %08X\n"
|
||||
# " default HH %08X\n"
|
||||
parser.add_argument("-r", "--resolution", help="Resolution")
|
||||
parser.set_defaults(audio=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
p = Player(**vars(args))
|
||||
p.run()
|
100
res/miracle-gst
Executable file
100
res/miracle-gst
Executable file
|
@ -0,0 +1,100 @@
|
|||
#!/bin/bash
|
||||
|
||||
function help {
|
||||
local scriptname="$(basename $0)"
|
||||
cat >&2 <<EOF
|
||||
|
||||
$scriptname [options]
|
||||
|
||||
play rtp stream
|
||||
|
||||
Options:
|
||||
-r Resolution
|
||||
-s <Width>x<height> Scale
|
||||
-d <level> Log level for gst
|
||||
-p <port> Port for stream
|
||||
-a Enables audio
|
||||
-h Show this help
|
||||
|
||||
Examples:
|
||||
|
||||
# play stream on port 7236
|
||||
$ $scriptname -p 7236
|
||||
# play stream with resolution 800x600
|
||||
$ $scriptname -s 800x600
|
||||
# play stream with audio
|
||||
$ $scriptname -a
|
||||
# play stream with debug level 3
|
||||
$ $scriptname -d 3
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
DEBUG='0'
|
||||
AUDIO='0'
|
||||
SCALE='0'
|
||||
|
||||
while getopts "r:d:as:p:h" optname
|
||||
do
|
||||
case "$optname" in
|
||||
"h")
|
||||
help
|
||||
exit 0
|
||||
;;
|
||||
"d")
|
||||
DEBUG=`echo ${OPTARG} | tr -d ' '`
|
||||
;;
|
||||
"r")
|
||||
RESOLUTION=`echo ${OPTARG} | tr -d ' '`
|
||||
;;
|
||||
"a")
|
||||
AUDIO='1'
|
||||
;;
|
||||
"p")
|
||||
PORT=`echo ${OPTARG} | tr -d ' '`
|
||||
;;
|
||||
"s")
|
||||
SCALE='1'
|
||||
WIDTH=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 1`
|
||||
HEIGHT=`echo ${OPTARG} | tr -d ' ' | cut -dx -f 2`
|
||||
;;
|
||||
"?")
|
||||
echo "Unknown option $OPTARG"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown parameter $OPTARG"
|
||||
help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
RUN="/usr/bin/gst-launch-1.0 -v "
|
||||
if [ $DEBUG != '0' ]
|
||||
then
|
||||
RUN+="--gst-debug=${DEBUG} "
|
||||
fi
|
||||
|
||||
RUN+="udpsrc port=$PORT caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=100 ! rtpmp2tdepay ! tsdemux "
|
||||
|
||||
if [ $AUDIO == '1' ]
|
||||
then
|
||||
RUN+="name=demuxer demuxer. "
|
||||
fi
|
||||
|
||||
RUN+="! queue max-size-buffers=0 max-size-time=0 ! h264parse ! avdec_h264 ! videoconvert ! "
|
||||
|
||||
if [ $SCALE == '1' ]
|
||||
then
|
||||
RUN+="videoscale method=1 ! video/x-raw,width=${WIDTH},height=${HEIGHT} ! "
|
||||
fi
|
||||
|
||||
RUN+="autovideosink "
|
||||
|
||||
if [ $AUDIO == '1' ]
|
||||
then
|
||||
RUN+="demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink "
|
||||
fi
|
||||
|
||||
echo "running: $RUN"
|
||||
exec ${RUN}
|
|
@ -35,7 +35,7 @@ Try installing packages "gst-plugins-bad, gst-plugins-base, gst-plugins-base-lib
|
|||
|
||||
If that fails too, try:
|
||||
|
||||
$ vlc rtp://@1991
|
||||
$ vlc rtp://@:1991
|
||||
|
||||
EOF
|
||||
else
|
||||
|
|
8
res/uibc-viewer
Executable file
8
res/uibc-viewer
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
IP=$1
|
||||
shift
|
||||
UIBC_PORT=$1
|
||||
shift
|
||||
|
||||
gstplayer $@ | miracle-uibcctl $IP $UIBC_PORT --daemon
|
Loading…
Add table
Add a link
Reference in a new issue