Skip to content

Commit

Permalink
Fresh cut
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Mattsson <[email protected]>
  • Loading branch information
datamattsson committed Dec 29, 2024
1 parent 4883810 commit d4932f8
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 44 deletions.
91 changes: 74 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,51 @@ Options:
--profile TEXT Profile name in --config
--mbps INTEGER Throttle encoder bitrate to Mbit/s, overrides bitrate in
`encoder` sections in --config. Use with caution.
--baseline Apply multiviewer baseline configuration suitable for
streaming.
--debug Turn on very verbose logging and override --config flag.
--help Show this message and exit.
```

Example configuration file.

```yaml
# Orei multi-viewer commands
# Orei multiviewer commands
# https://cdn.shopify.com/s/files/1/1988/4253/files/UHD-401MV-Updated_User_Manual.pdf?v=1672745852

# Magewell encoder API
# https://www.magewell.com/api-docs/pro-convert-encoder-api/
---
config:
local:
# If serial_port can't be found, the .config.mv.ip_addr will be used
serial_port: /dev/cu.usbserial-3
# set to true to ignore encoder when local serial is connected
ignore_encoder: true
debug: true
# Set to true to ignore encoder and encoder sections
ignore_encoder: false
# Do not set to true in production
debug: false
mv:
# Set your preferences
ip_addr: 192.168.37.5
# run `./cli.py --baseline --config config.yaml` to apply baseline on a new
# unit, like disabling HDCP, OSD, auto-switching and window borders.
# vka sets the background color when applying the baseline config
vka: blue # video keep active pattern, blue or black.

# tunables
remote_timeout: 2.0 # Positive float timeout in seconds for IP-based command
# If null is given, the socket is put in blocking mode
command_delay: .1 # Positive float delay after sending a MV command
# Tune this if timeouts are being hit on quad layouts
modes:
3840x2160p60: 3
3840x2160p30: 5
1920x1080p60: 8
scenes:
single: 1
pip: 2 # Not tested
pbp: 3 # Not tested
triple: 4
quad: 5

# All encoder sections are optional if you don't have a compatible encoder.
encoder:
ip_addr: 192.168.37.4
user: Admin
# md5 of your password (this is Admin)
pass: e3afed0047b08059d0fada10f400c1e5
profiles:
default:
main:
mv:
# Keyed from with config.mv.scenes
scene: quad
Expand Down Expand Up @@ -95,14 +98,42 @@ profiles:
framerate: raw
# 1920x1080 etc, raw means match input
resolution: 1920x1080
playfield:
mv:
# Keyed from with config.mv.scenes
scene: pip
# Keyed from with config.mv.modes
output: 3840x2160p60
# Consume key (HDMI input) with value (viewport output)
layout:
HDMI-4: 1 # bottom input
HDMI-2: 2 # inlay input
audio: 3
pip: # percentages
size: 25
pos-x: 2
pos-y: 3
encoder:
# 50 - 200 "quality" slider, default is 100, ignored when --mbps is used
bitrate: 200
# frame rate, raw matches input, half, one-third, quarter are valid
framerate: raw
# 1920x1080 etc, raw means match input
resolution: raw
```
## Prequisites and Hardware SKUs
RigC has been developed on a Mac and will most likely just work on Mac and Linux at this time.
- Python scripting needs to be activated in OBS (find instructions for this elsewhere)
- HDCP needs to be disabled on the multiviewer prior use with a capture card
- Python scripting needs to be activated in OBS (find instructions for this elsewhere).
- HDCP needs to be disabled on the multiviewer prior use with a capture card.
RigC now allows baseline configuration of a brand new multiviewer. It will disable HDCP and set other useful defaults.
```
./cli.py --baseline --config config.yaml --debug
```

### Tested (and should work) Hardware

Expand All @@ -116,6 +147,13 @@ These share the same MCU on paper.
- Monoprice Blackbird Pro Series 4K60 Multiviewer (not tested)
- NEUVIDEO 4x1 4K60 UHD Quad/PiP/PoP Multiviewer (not tested)

It's recommended to make sure the latest firmware of the MCU and scaler is installed. The latest version of RigC is tested against:

```
MCU FW version 1.00.12
SCALER FW version 20240328-11
```

#### Serial servers

Any serial server that expose the serial interface over Telnet (port 23) should work.
Expand All @@ -127,6 +165,8 @@ These are the ones I've gone through.
- StarTech NETRS2321P
- Works out of the box without any configuration except setting an IP address.

**Tip:** If you're looking for a quick and dirty solution to just run a few commands on the multiviewer, check out [CoolTerm](https://coolterm.macupdate.com/). It works for both local and remote serial interfaces and great for debugging.

#### Encoders

Magewell Pro Converters all have the same API. The Ultra Encode series would need work.
Expand All @@ -139,10 +179,14 @@ Magewell Pro Converters all have the same API. The Ultra Encode series would nee
- Magewell Pro Convert SDI Plus
- Magewell Pro Convert SDI TX

**Note:** RigC does NOT require an NDI encoder to operate.

## Marketecture

![](assets/README/marketecture.png)

Using the serial interface is optional and RigC can configure a quad or triple view as a set-it-and-forget-it on the multiviewer. The output may be captured by either an encoder remotely or HDMI locally. In such configuration, RigC does not even need to be installed into OBS and can merely be used as a multiviewer configuration tool.

## Demos

A short snippet that describes the benefit of using 4K cameras in single view modes.
Expand Down Expand Up @@ -203,6 +247,17 @@ Next, hit that `+` sign and find `rigc.py` in the `rigc` repository.

Please use [issues](/datamattsson/rigc/issues) to report potential issues and ask questions.

## Known Issues and Limitations

Major annoyances and workarounds.

- RigC is inserted synchronously after the scene transition and needs to return an exit code to OBS before becomes usable again. Under normal circumstances this takes less then half a second and is not noticeable by the broadcaster. You will get the spinning beach ball that will eventually freeze the program until RigC times out. Asynchronous execution is on the roadmap for RigC. See also: Next bullet.
- Do NOT run RigC with `--debug` enabled in production. In certain circumstances the serial port read operation gets stuck and takes up to a minute to timeout. Since the OBS script is synchronous, the program will be impacted after a few seconds. Without `--debug`, the serial port is never read, only written to.
- Do NOT run RigC with `--mbps` enabled in production. It's highly volatile and may lock up OBS if configured inappropriately.
- Make sure you have the latest firmware installed for your devices. Contact the vendor to ensure your device is updated, this includes both the encoder and multiviewer.
- Since RigC is inserted AFTER the scene transition the program will see some ugly switching for around half a second. To cover up this, I'm using "Image Slideshows" with an animated GIF that substitutes the scene transition as it runs immediately BEFORE the scene transition and smooths over the switching.
- OBS keeps the names of profiles in the configuration pane dropdowns. If you're switching different configuration files for different purposes, make sure the profile names overlap otherwise you'll lose the dropdown selections. I learned this the hard way.

# Background

In the world of pinball streaming you are dependent of having at least three cameras capturing talent, playfield and score boards wirelessly in a very confined space. A multiviewer is a great device to ingest HDMI signals and output a "grid" of inputs to OBS. The principal caveat of using such a solution is that the input will be a quarter of the original resolution and there is some loss of quality when working with 1080p60 if scaling is involved (I.e stretching 960 to 1080 for the playfield camera). The most common adapted solution is to run one wireless HDMI transmitter/receiver pair per camera but it comes with its own set of challenges.
Expand All @@ -213,6 +268,8 @@ Running full NDI over WiFi is considered by industry professionals the dumbest i

A purpose built 2160p60 wireless HDMI transmitter/receiver product from Teradek cost $6,490 last time I checked. RigC is the solution until competition has caught up with Teradek to make 2160p60 wireless affordable and feasible for pinball streaming, RigC costs about a third and is way more flexible than a dedicated 2160p60 transmitter/receiver.

**Edit 2024-12-28**: Accsoon have released an affordable 2160p60 transmitter/receiver ([CineView Master 4K](https://accsoonusa.com/page/cineview-4k/)) that retails for around $900 with the meager bandwidth of 12Mbit/s and it's nowhere near where it needs to be for high definition tournament pinball streaming using a multiviewer.

# License, Contribute and Support RigC

RigC is available under the [MIT](LICENSE) license.
Expand Down
31 changes: 23 additions & 8 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@
datefmt='%a, %d %b %Y %H:%M:%S +0000')

class PinballRig:
def __init__(self, config, mbps):
def __init__(self, config, mbps, baseline):
self.config = config
self.mbps = mbps
self.baseline = baseline
self.logger = logging.getLogger(f'{__name__}')
self.logger.setLevel(logging.DEBUG if self.config.get(
'local').get('debug') else logging.INFO)

def configure(self, config, profile):
try:
mv = uhdmcu.FourXOneUHD(config)
mv.apply(profile['mv'])
if profile:
mv.apply(profile.get('mv'))
else:
mv.baseline()
return
except Exception as exc:
self.logger.error(f'Unable to apply MV config: "{exc}"')
raise RigBroke from exc
if not config['local']['ignore_encoder'] or not os.path.exists(config['local']['serial_port']):
if not self.config.get('local').get('ignore_encoder') and profile.get('encoder'):
try:
enc = magewell.ProConvert(config, self.mbps)
enc.apply(profile['encoder'])
enc.apply(profile.get('encoder'))
except Exception as exc:
self.logger.error(f'Unable to apply Encoder config: {exc}')
raise RigBroke from exc
Expand All @@ -43,9 +49,10 @@ class RigBroke(Exception):
default='config.yaml', help='config.yaml formatted file')
@click.option('--profile', default='default', help='Profile name in --config')
@click.option('--mbps', default=0, help='Throttle encoder bitrate to Mbit/s, overrides bitrate in `encoder` sections in --config. Use with caution.')
@click.option('--baseline', default=False, help='Apply multiviewer baseline configuration suitable for streaming.', is_flag=True)
@click.option('--debug', default=False, help='Turn on very verbose logging and override --config flag.', is_flag=True)

def apply(config, profile, mbps, debug):
def apply(config, profile, mbps, debug, baseline):

logger = logging.getLogger(f'{__name__}')
if debug:
Expand All @@ -64,18 +71,26 @@ def apply(config, profile, mbps, debug):
if debug:
config['local']['debug'] = True

if config and profiles.get(profile):
rig = PinballRig(config, mbps)
if config and profiles.get(profile) and not baseline:
rig = PinballRig(config, mbps, baseline)
try:
rig.configure(config, profiles.get(profile))
logger.debug(f'Rig configured with profile: "{profile}"')
except RigBroke:
logger.error('Failed configuring the rig, check logs.')
sys.exit(1)
elif config and baseline:
rig = PinballRig(config, mbps, baseline)
try:
rig.configure(config, False)
logger.debug(f'Rig baseline configured.')
except RigBroke:
logger.error('Failed applying baseline, check logs.')
sys.exit(1)
else:
logger.error(f'The `config` stanza or profile "{profile}" in the `profiles` \
stanza invalid in --config file')
sys.exit(1)

if __name__ == '__main__':
apply(None, None, None, None)
apply(None, None, None, None, None)
53 changes: 38 additions & 15 deletions config.yaml-dist
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
# Orei multi-viewer commands
# Orei multiviewer commands
# https://cdn.shopify.com/s/files/1/1988/4253/files/UHD-401MV-Updated_User_Manual.pdf?v=1672745852

# Magewell encoder API
# https://www.magewell.com/api-docs/pro-convert-encoder-api/
---
config:
local:
# If serial_port can't be found, the .config.mv.ip_addr will be used
serial_port: /dev/cu.usbserial-3
# set to true to ignore encoder when local serial is connected
ignore_encoder: true
debug: true
# Set to true to ignore encoder and encoder sections
ignore_encoder: false
# Do not set to true in production
debug: false
mv:
# Set your preferences
ip_addr: 192.168.37.5
# run `./cli.py --baseline --config config.yaml` to apply baseline on a new
# unit, like disabling HDCP, OSD, auto-switching and window borders.
# vka sets the background color when applying the baseline config
vka: blue # video keep active pattern, blue or black.

# tunables
remote_timeout: 2.0 # Positive float timeout in seconds for IP-based command
# If null is given, the socket is put in blocking mode
command_delay: .1 # Positive float delay after sending a MV command
# Tune this if timeouts are being hit on quad layouts
modes:
3840x2160p60: 3
3840x2160p30: 5
1920x1080p60: 8
scenes:
single: 1
pip: 2 # Not tested
pbp: 3 # Not tested
triple: 4
quad: 5

# All encoder sections are optional if you don't have a compatible encoder.
encoder:
ip_addr: 192.168.37.4
user: Admin
# md5 of your password (this is Admin)
pass: e3afed0047b08059d0fada10f400c1e5
profiles:
default:
main:
mv:
# Keyed from with config.mv.scenes
scene: quad
Expand Down Expand Up @@ -69,3 +70,25 @@ profiles:
framerate: raw
# 1920x1080 etc, raw means match input
resolution: 1920x1080
playfield:
mv:
# Keyed from with config.mv.scenes
scene: pip
# Keyed from with config.mv.modes
output: 3840x2160p60
# Consume key (HDMI input) with value (viewport output)
layout:
HDMI-4: 1 # bottom input
HDMI-2: 2 # inlay input
audio: 3
pip: # percentages
size: 25
pos-x: 2
pos-y: 3
encoder:
# 50 - 200 "quality" slider, default is 100, ignored when --mbps is used
bitrate: 200
# frame rate, raw matches input, half, one-third, quarter are valid
framerate: raw
# 1920x1080 etc, raw means match input
resolution: raw
Loading

0 comments on commit d4932f8

Please sign in to comment.