Skip to content

Commit

Permalink
Started implementation from scratch, working player and networking br…
Browse files Browse the repository at this point in the history
…oadcast
  • Loading branch information
reinzor committed Aug 22, 2018
0 parents commit 1d91f08
Show file tree
Hide file tree
Showing 22 changed files with 560 additions and 0 deletions.
106 changes: 106 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

.idea

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Videowall (WIP)

Video wall with multiple tiles that enables synchronized video playback, mirrored or tiled.

## Installation

### Raspberry PI installation

#### Installation prerequisites

- Raspbian Jessie
- Raspberry Pi 3 / Raspberry Pi Zero (other Pi's not tested)
- Videowall repository is your current working directory

#### Installation dependencies

Install the `x-window-system` and `gstreamer`:

```
sudo apt-get -y install x-window-system \
gstreamer1.0-tools \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
gstreamer1.0-omx \
gir1.2-gst-plugins-base-1.0 \
python-gst-1.0 \
pulseaudio \
python-enum \
python-colorlog
```

Make sure `gstreamer is working properly` by downloading and playing an x264 encoded sample video:

```
wget https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_30mb.mp4 -O ~/big_buck_bunny_720p_30mb.mp4
```

#### Set-up videowall

Enable the x-server on startup:

```
sudo cp systemd/startx/override.conf /etc/systemd/system/[email protected]/
sudo systemctl daemon-reload
sudo systemctl restart [email protected]
```

If everything went well, the x server should now be running and you should see a black screen with a small green font: `pi@raspberrypi`.

Add the following to `/etc/X11/xinit/xinitrc` after the first line to disable screen blanking:
```
xset s off # don't activate screensaver
xset -dpms # disable DPMS (Energy Star) features.
xset s noblank # don't blank the video device
```

Set boot config RPI so we can use more GPU memory (not sure whether this has any effect):
```
echo "gpu_mem = 386MB" | sudo tee -a /boot/config.txt
```

## Quick start

### Server

scripts/server

### Client

scripts/client

## References

- [Gstreamer mmal for smooth video playback on RPI](https://gstreamer.freedesktop.org/data/events/gstreamer-conference/2016/John%20Sadler%20-%20Smooth%20video%20on%20Raspberry%20Pi%20with%20gst-mmal%20(Lightning%20Talk).pdf)
- [Gstreamer sync server for synchronized playback on multiple client with a gstreamer client server set-up](https://github.com/ford-prefect/gst-sync-server)
- [Multicast Video-Streaming on Embedded Linux Environment, Daichi Fukui, Toshiba Corporation, Japan Technical Jamboree 63, Dec 1st, 2017](https://elinux.org/images/3/33/Multicast_jamboree63_fukui.pdf)
- [omxplayer-sync](https://github.com/turingmachine/omxplayer-sync)
- [pwomxplayer](https://github.com/JeffCost/pwomxplayer)
- [dbus vlc](https://wiki.videolan.org/DBus-spec/)
- [dbus tutorial phython for MPRIS](http://amhndu.github.io/Blog/python-dbus-mpris.html)
- [dbus omxplayer](https://github.com/popcornmix/omxplayer)
- [GPU memory 90 degrees omxplayer](https://github.com/popcornmix/omxplayer/issues/467)
- [Remote dbus control](https://stackoverflow.com/questions/10158684/connecting-to-dbus-over-tcp/13275973#13275973)
Empty file added scripts/client
Empty file.
57 changes: 57 additions & 0 deletions scripts/server
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python


import sys
from players import MasterPlayer
from gi.repository import GObject
import json

import time
import threading
import select
import socket


class MasterThread(threading.Thread):
def __init__(self, uri, broadcast_port, player_ip, player_port):
super(MasterThread, self).__init__()
self.master_player = MasterPlayer(uri, player_ip, player_port)
self.broadcast_port = broadcast_port

self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

def send_update(self):
pdu = {
'base-time': self.master_player.base_time,
'uri': self.master_player.uri,
'player-ip': self.master_player.ip,
'player-port': self.master_player.port,
}
self.socket.sendto(json.dumps(pdu), ('<broadcast>', self.broadcast_port))

def run(self):
self.master_player.play()
while True:
self.send_update()
time.sleep(1)


from argparse import ArgumentParser

if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('uri')
parser.add_argument('--broadcast_port', type=int, default=37020)
parser.add_argument('--player_ip', default='localhost')
parser.add_argument('--player_port', type=int, default=11111)

args = parser.parse_args()

GObject.threads_init()
loop = GObject.MainLoop()

master = MasterThread(args.uri, args.broadcast_port, args.player_ip, args.player_port)
master.start()

loop.run()
29 changes: 29 additions & 0 deletions src/videowall/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import gi
import logging.config

from client import Client
from server import Server

gi.require_version('Gst', '1.0')
gi.require_version('GstNet', '1.0')

logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'colored': {
'()': 'colorlog.ColoredFormatter',
'format': "%(log_color)s[%(levelname)s] %(name)s: %(message)s",
}
},
'handlers': {
'stream': {
'class': 'logging.StreamHandler',
'formatter': 'colored',
},
},
'root': {
'handlers': ['stream'],
'level': 'INFO',
},
})
3 changes: 3 additions & 0 deletions src/videowall/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Client(object):
def __init__(self):
pass
2 changes: 2 additions & 0 deletions src/videowall/networking/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from networking_client import NetworkingClient
from networking_server import NetworkingServer
3 changes: 3 additions & 0 deletions src/videowall/networking/message_definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from collections import namedtuple

BroadcastMessage = namedtuple("BroadcastMessage", ("filename", "base_time", "player_ip", "player_port"))
27 changes: 27 additions & 0 deletions src/videowall/networking/networking_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json
import logging
import socket

from message_definition import BroadcastMessage
from networking_exceptions import NetworkingException

logger = logging.getLogger(__name__)


class NetworkingClient(object):
def __init__(self, broadcast_port, buffer_size=1024):
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self._socket.bind(("", broadcast_port)) # Bind to all
self._buffer_size = buffer_size

def receive_broadcast(self):
data, _ = self._socket.recvfrom(self._buffer_size)

try:
msg = BroadcastMessage(**json.loads(data))
except Exception as e:
raise NetworkingException(e)
else:
logger.debug("Received %s", msg)
return msg
2 changes: 2 additions & 0 deletions src/videowall/networking/networking_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class NetworkingException(Exception):
pass
22 changes: 22 additions & 0 deletions src/videowall/networking/networking_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import json
import socket
import logging

from .message_definition import BroadcastMessage
from .networking_exceptions import NetworkingException

logger = logging.getLogger(__name__)


class NetworkingServer(object):
def __init__(self, broadcast_port):
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self._broadcast_port = broadcast_port

def send_broadcast(self, msg):
if not isinstance(msg, BroadcastMessage):
raise NetworkingException("msg ({}) is not of type NetworkingException".format(msg))

logger.debug("Sending %s", msg)
self._socket.sendto(json.dumps(msg._asdict()), ('<broadcast>', self._broadcast_port))
3 changes: 3 additions & 0 deletions src/videowall/players/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from player_client import PlayerClient
from player_server import PlayerServer
from player_platforms import PlayerPlatform
Loading

0 comments on commit 1d91f08

Please sign in to comment.