Skip to content

Instantly share code, notes, and snippets.

@dasl-
Created November 9, 2021 04:48
Show Gist options
  • Save dasl-/d5bed823af668a4b677410c707343996 to your computer and use it in GitHub Desktop.
Save dasl-/d5bed823af668a4b677410c707343996 to your computer and use it in GitHub Desktop.
diff --git a/docs/configuring_omxplayer.adoc b/docs/configuring_omxplayer.adoc
index 5369839..942002d 100644
--- a/docs/configuring_omxplayer.adoc
+++ b/docs/configuring_omxplayer.adoc
@@ -8,12 +8,6 @@ At time of writing, https://github.com/dasl-/piwall2/blob/5625b8887f528f671b7944
omxplayer --crop {0} --adev {1} --display {2} --vol {3} --aspect-mode stretch --no-keys --timeout 30 --threshold 0.2 --video_fifo 35 --genlog pipe:0
....
-Of particular note are the `threshold` and `video_fifo` options:
-....
- --threshold n Amount of buffered data required to finish buffering [s]
- --video_fifo n Size of video output fifo in MB
-....
-
Here is some https://github.com/popcornmix/omxplayer/issues/256[more info about some of the more interesting omxplayer parameters].
## omxplayer configuration
@@ -34,10 +28,17 @@ In the omxplayer log, we will see https://gist.github.com/dasl-/0caa95c6c438685b
Raising the timeout should make these types of errors less likely. See also https://github.com/dasl-/piwall2/blob/main/docs/issues_weve_seen_before.adoc#receivers-sometimes-fail-to-play-video
### threshold
-Raising the `threshold` was necessary to prevent random occasional video drop outs. See: https://github.com/dasl-/piwall2/blob/main/docs/profiling_and_debugging_multicast_video_playback.adoc . Hopefully setting it to 5 seconds is enough to accomodate playing of all videos without pauses / drop outs.
+....
+ --threshold n Amount of buffered data required to finish buffering [s]
+....
+
+Although we set it explicitly to 0.2s, this is the same as the https://github.com/popcornmix/omxplayer/blob/1f1d0ccd65d3a1caa86dc79d2863a8f067c8e3f8/omxplayer.cpp#L1177[default threshold value].
### video_fifo
-Increasing the `video_fifo` option was necessary to accomodate the increased threshold. Without increasing the video_fifo, we'd see videos with a threshold of 5 seconds take about 90 seconds to start playing. In other words, they were taking about 85 seconds longer than expected to start playing. What were they doing all this time? Let's look at a https://gist.github.com/dasl-/0e52feccff6caacecf0955011f925aeb[log from such a run]. From the logs, it looks like we started receiving video signal at around 02:46:07, yet it took until 02:47:39 (about 90 seconds) for the video to actually unpause:
+....
+ --video_fifo n Size of video output fifo in MB
+....
+If using a `--threshold` value that is much higher than the default, it may be necessary to increase the `video_fifo` size. Without increasing the `video_fifo`, we'd see videos with a `threshold` of 5 seconds take about 90 seconds to start playing. In other words, they were taking about 85 seconds longer than expected to start playing. What were they doing all this time? Let's look at a https://gist.github.com/dasl-/0e52feccff6caacecf0955011f925aeb[log from such a run]. From the logs, it looks like we started receiving video signal at around 02:46:07, yet it took until 02:47:39 (about 90 seconds) for the video to actually unpause:
....
02:47:39 T:2123221681 DEBUG: OMXClock::OMXSetSpeed(1.00) pause_resume:1
....
@@ -49,6 +50,8 @@ If we https://gist.github.com/dasl-/1b0070adf0dbcaca22986d2f33afe88e[filter for
Increasing the `video_fifo` size appears to solve the problem, indicating that omxplayer likely didn't have enough memory allocated to buffer the requested 5 seconds of video. Presumably it gave up after about 90 seconds and started playing anyway. By increasing the buffer, we only have to wait about 5 seconds for the video to start playing.
+The default `video_fifo` size is https://github.com/popcornmix/omxplayer/blob/1f1d0ccd65d3a1caa86dc79d2863a8f067c8e3f8/OMXVideo.h#L83[`4.6875`].
+
### audio_fifo
If we ever need to increase the `threshold` more, we may need to raise `video_fifo` again. Note that according to the logs, the audio buffers didn't go very far past 6.33 seconds. If we ever need to raise the `threshold` beyond that, we may need to increase audio buffer sizes also. Using the `audio_fifo` option to omxplayer didn't seem to have any effect, however. Reading the omxplayer source, it actually seemed like the `audio_fifo` option might not get used for anything, i.e. it's a no-op? Worth reconfirming sometime.
diff --git a/piwall2/broadcaster/videobroadcaster.py b/piwall2/broadcaster/videobroadcaster.py
index cffe3e3..509d433 100644
--- a/piwall2/broadcaster/videobroadcaster.py
+++ b/piwall2/broadcaster/videobroadcaster.py
@@ -20,8 +20,8 @@ class VideoBroadcaster:
END_OF_VIDEO_MAGIC_BYTES = b'PIWALL2_END_OF_VIDEO_MAGIC_BYTES'
- __VIDEO_URL_TYPE_YOUTUBEDL = 'video_url_type_youtubedl'
- __VIDEO_URL_TYPE_FILE = 'video_url_type_file'
+ __VIDEO_URL_TYPE_YOUTUBE = 'video_url_type_youtube'
+ __VIDEO_URL_TYPE_LOCAL_FILE = 'video_url_type_local_file'
__AUDIO_FORMAT = 'bestaudio'
# Touch this file when video playing is done.
@@ -82,6 +82,8 @@ class VideoBroadcaster:
def __broadcast_internal(self):
self.__logger.info(f"Starting broadcast for: {self.__video_url}")
+ self.__start_receivers()
+
"""
What's going on here? We invoke youtube-dl (ytdl) three times in the broadcast code:
1) To populate video metadata, including dimensions which allow us to know how much to crop the video
@@ -114,7 +116,6 @@ class VideoBroadcaster:
"""
download_and_convert_video_proc = self.__start_download_and_convert_video_proc()
self.__get_video_info(assert_data_not_yet_loaded = True)
- self.__start_receivers()
"""
This `sleep` makes the videos more likely to start in-sync across all the TVs, but I'm not totally
@@ -132,7 +133,7 @@ class VideoBroadcaster:
See data collected on the effectiveness of this sleep:
https://gist.github.com/dasl-/e5c05bf89c7a92d43881a2ff978dc889
"""
- time.sleep(2)
+ # time.sleep(2)
video_broadcast_proc = self.__start_video_broadcast_proc(download_and_convert_video_proc)
self.__logger.info("Waiting for download_and_convert_video and video_broadcast procs to end...")
@@ -176,7 +177,7 @@ class VideoBroadcaster:
ffmpeg_input_clause = self.__get_ffmpeg_input_clause()
audio_clause = '-c:a mp2 -b:a 192k' # TODO: is this necessary? Can we use mp3?
- if self.__get_video_url_type() == self.__VIDEO_URL_TYPE_FILE:
+ if self.__get_video_url_type() == self.__VIDEO_URL_TYPE_LOCAL_FILE:
# Don't transcode audio if we don't need to
audio_clause = '-c:a copy'
@@ -195,6 +196,13 @@ class VideoBroadcaster:
return download_and_convert_video_proc
def __start_video_broadcast_proc(self, download_and_convert_video_proc):
+ msg = {
+ 'video_width': self.__get_video_info()['width'],
+ 'video_height': self.__get_video_info()['height'],
+ }
+ self.__control_message_helper.send_msg(ControlMessageHelper.TYPE_VIDEO_DIMENSIONS, msg)
+ self.__logger.info("Sent video_dimensions control message.")
+
# See: https://github.com/dasl-/piwall2/blob/main/docs/controlling_video_broadcast_speed.adoc
mbuffer_size = round(Receiver.VIDEO_PLAYBACK_MBUFFER_SIZE_BYTES / 2)
burst_throttling_clause = (f'HOME=/home/pi mbuffer -q -l /tmp/mbuffer.out -m {mbuffer_size}b | ' +
@@ -222,8 +230,7 @@ class VideoBroadcaster:
def __start_receivers(self):
msg = {
'log_uuid': Logger.get_uuid(),
- 'video_width': self.__get_video_info()['width'],
- 'video_height': self.__get_video_info()['height'],
+ 'video_url_type': self.__get_video_url_type()
}
self.__control_message_helper.send_msg(ControlMessageHelper.TYPE_PLAY_VIDEO, msg)
self.__logger.info("Sent play_video control message.")
@@ -242,7 +249,7 @@ class VideoBroadcaster:
def __get_ffmpeg_input_clause(self):
video_url_type = self.__get_video_url_type()
- if video_url_type == self.__VIDEO_URL_TYPE_YOUTUBEDL:
+ if video_url_type == self.__VIDEO_URL_TYPE_YOUTUBE:
"""
Pipe to mbuffer to avoid video drop outs when youtube-dl temporarily loses its connection
and is trying to reconnect:
@@ -294,7 +301,7 @@ class VideoBroadcaster:
)
return f"-i <({youtube_dl_video_cmd}) -i <({youtube_dl_audio_cmd})"
- elif video_url_type == self.__VIDEO_URL_TYPE_FILE:
+ elif video_url_type == self.__VIDEO_URL_TYPE_LOCAL_FILE:
return f"-i {shlex.quote(self.__video_url)} "
# Lazily populate video_info from youtube. This takes a couple seconds, as it invokes youtube-dl on the video.
@@ -306,7 +313,7 @@ class VideoBroadcaster:
return self.__video_info
video_url_type = self.__get_video_url_type()
- if video_url_type == self.__VIDEO_URL_TYPE_YOUTUBEDL:
+ if video_url_type == self.__VIDEO_URL_TYPE_YOUTUBE:
self.__logger.info("Downloading and populating video metadata...")
ydl_opts = {
'format': self.__config_loader.get_youtube_dl_video_format(),
@@ -347,7 +354,7 @@ class VideoBroadcaster:
self.__logger.info(f"Using: {self.__video_info['vcodec']} / {self.__video_info['ext']}@" +
f"{self.__video_info['width']}x{self.__video_info['height']}")
- elif video_url_type == self.__VIDEO_URL_TYPE_FILE:
+ elif video_url_type == self.__VIDEO_URL_TYPE_LOCAL_FILE:
# TODO: guard against unsupported video formats
ffprobe_cmd = ('ffprobe -hide_banner -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=width,height ' +
shlex.quote(self.__video_url))
@@ -365,9 +372,9 @@ class VideoBroadcaster:
def __get_video_url_type(self):
if self.__video_url.startswith('http://') or self.__video_url.startswith('https://'):
- return self.__VIDEO_URL_TYPE_YOUTUBEDL
+ return self.__VIDEO_URL_TYPE_YOUTUBE
else:
- return self.__VIDEO_URL_TYPE_FILE
+ return self.__VIDEO_URL_TYPE_LOCAL_FILE
def __update_youtube_dl(self):
update_youtube_dl_output = (subprocess
diff --git a/piwall2/controlmessagehelper.py b/piwall2/controlmessagehelper.py
index 3be55ef..783227e 100644
--- a/piwall2/controlmessagehelper.py
+++ b/piwall2/controlmessagehelper.py
@@ -16,6 +16,7 @@ class ControlMessageHelper:
TYPE_PLAY_VIDEO = 'play_video'
TYPE_SKIP_VIDEO = 'skip_video'
TYPE_DISPLAY_MODE = 'display_mode'
+ TYPE_VIDEO_DIMENSIONS = 'video_dimensions'
CTRL_MSG_TYPE_KEY = 'msg_type'
CONTENT_KEY = 'content'
diff --git a/piwall2/displaymode.py b/piwall2/displaymode.py
index 6664fb0..02e552d 100644
--- a/piwall2/displaymode.py
+++ b/piwall2/displaymode.py
@@ -5,3 +5,5 @@ class DisplayMode:
# Repeat mode is like this: https://i.imgur.com/cpS61s8.png
DISPLAY_MODE_TILE = 'DISPLAY_MODE_TILE'
DISPLAY_MODE_REPEAT = 'DISPLAY_MODE_REPEAT'
+
+ DISPLAY_MODES = (DISPLAY_MODE_TILE, DISPLAY_MODE_REPEAT)
diff --git a/piwall2/receiver/receiver.py b/piwall2/receiver/receiver.py
index bcbcb46..7ccc6fc 100644
--- a/piwall2/receiver/receiver.py
+++ b/piwall2/receiver/receiver.py
@@ -19,11 +19,6 @@ class Receiver:
VIDEO_PLAYBACK_MBUFFER_SIZE_BYTES = 1024 * 1024 * 400 # 400 MB
- __DEFAULT_CROP_ARGS = {
- DisplayMode.DISPLAY_MODE_TILE: None,
- DisplayMode.DISPLAY_MODE_REPEAT: None,
- }
-
def __init__(self):
self.__logger = Logger().set_namespace(self.__class__.__name__)
self.__logger.info("Started receiver!")
@@ -34,10 +29,8 @@ class Receiver:
self.__display_mode = DisplayMode.DISPLAY_MODE_TILE
self.__display_mode2 = DisplayMode.DISPLAY_MODE_TILE
- # Crop arguments to send to omxplayer for the currently playing video if the display mode changes.
- # These change per video, thus we just initialize them to dummy values in the constructor.
- self.__crop_args = self.__DEFAULT_CROP_ARGS
- self.__crop_args2 = self.__DEFAULT_CROP_ARGS
+ self.__crop_args = None
+ self.__crop_args2 = None
config_loader = ConfigLoader()
self.__receiver_config_stanza = config_loader.get_own_receiver_config_stanza()
@@ -89,6 +82,9 @@ class Receiver:
self.__stop_video_playback_if_playing()
self.__receive_and_play_video_proc = self.__receive_and_play_video(ctrl_msg)
self.__receive_and_play_video_proc_pgid = os.getpgid(self.__receive_and_play_video_proc.pid)
+ elif msg_type == ControlMessageHelper.TYPE_VIDEO_DIMENSIONS:
+ if self.__is_video_playback_in_progress:
+ self.__set_video_crop_args(ctrl_msg)
elif msg_type == ControlMessageHelper.TYPE_SKIP_VIDEO:
if self.__is_video_playback_in_progress:
self.__stop_video_playback_if_playing()
@@ -101,29 +97,25 @@ class Receiver:
old_display_mode = self.__display_mode
old_display_mode2 = self.__display_mode2
for tv_num, tv_id in self.__tv_ids.items():
+ display_mode_to_set = display_mode_by_tv_id[tv_id]
+ if display_mode_to_set not in DisplayMode.DISPLAY_MODES:
+ display_mode_to_set = DisplayMode.DISPLAY_MODE_TILE
if tv_id in display_mode_by_tv_id:
if tv_num == 1:
- self.__display_mode = display_mode_by_tv_id[tv_id]
+ self.__display_mode = display_mode_to_set
else:
- self.__display_mode2 = display_mode_by_tv_id[tv_id]
- if self.__is_video_playback_in_progress and old_display_mode != self.__display_mode:
- if self.__display_mode == DisplayMode.DISPLAY_MODE_REPEAT:
- self.__omxplayer_controller.set_crop(self.__crop_args[DisplayMode.DISPLAY_MODE_REPEAT])
- else:
- self.__omxplayer_controller.set_crop(self.__crop_args[DisplayMode.DISPLAY_MODE_TILE])
- if self.__is_video_playback_in_progress and old_display_mode2 != self.__display_mode2:
- pass # TODO
+ self.__display_mode2 = display_mode_to_set
+ if self.__is_video_playback_in_progress and self.__crop_args and old_display_mode != self.__display_mode:
+ self.__omxplayer_controller.set_crop(self.__crop_args[self.__display_mode])
+ if self.__is_video_playback_in_progress and self.__crop_args2 and old_display_mode2 != self.__display_mode2:
+ pass # TODO display_mode2 with a second dbus interface name
def __receive_and_play_video(self, ctrl_msg):
ctrl_msg_content = ctrl_msg[ControlMessageHelper.CONTENT_KEY]
self.__orig_log_uuid = Logger.get_uuid()
Logger.set_uuid(ctrl_msg_content['log_uuid'])
- cmd, self.__crop_args, self.__crop_args2 = (
- self.__receiver_command_builder.build_receive_and_play_video_command_and_get_crop_args(
- ctrl_msg_content['log_uuid'], ctrl_msg_content['video_width'],
- ctrl_msg_content['video_height'], self.__video_player_volume_pct,
- self.__display_mode, self.__display_mode2
- )
+ cmd = self.__receiver_command_builder.build_receive_and_play_video_command(
+ ctrl_msg_content['log_uuid'], self.__video_player_volume_pct
)
self.__logger.info(f"Running receive_and_play_video command: {cmd}")
self.__is_video_playback_in_progress = True
@@ -132,6 +124,13 @@ class Receiver:
)
return proc
+ def __set_video_crop_args(self, ctrl_msg):
+ ctrl_msg_content = ctrl_msg[ControlMessageHelper.CONTENT_KEY]
+ self.__crop_args, self.__crop_args2 = self.__receiver_command_builder.get_crop_dimensions(
+ ctrl_msg_content['video_width'], ctrl_msg_content['video_height']
+ )
+ self.__omxplayer_controller.set_crop(self.__crop_args[self.__display_mode])
+
def __stop_video_playback_if_playing(self):
if not self.__is_video_playback_in_progress:
return
@@ -144,6 +143,8 @@ class Receiver:
pass
Logger.set_uuid(self.__orig_log_uuid)
self.__is_video_playback_in_progress = False
+ self.__crop_args = None
+ self.__crop_args2 = None
# The first video that is played after a system restart appears to have a lag in starting,
# which can affect video synchronization across the receivers. Ensure we have played at
diff --git a/piwall2/receiver/receivercommandbuilder.py b/piwall2/receiver/receivercommandbuilder.py
index b9e4437..d4c3f64 100644
--- a/piwall2/receiver/receivercommandbuilder.py
+++ b/piwall2/receiver/receivercommandbuilder.py
@@ -15,18 +15,12 @@ class ReceiverCommandBuilder:
self.__config_loader = config_loader
self.__receiver_config_stanza = receiver_config_stanza
- def build_receive_and_play_video_command_and_get_crop_args(
- self, log_uuid, video_width, video_height, volume_pct, display_mode, display_mode2
- ):
+ def build_receive_and_play_video_command(self, log_uuid, volume_pct):
adev, adev2 = self.__get_video_command_adev_args()
display, display2 = self.__get_video_command_display_args()
- crop_args, crop_args2 = self.__get_video_command_crop_args(video_width, video_height)
- crop = crop_args[display_mode]
- crop2 = crop_args2[display_mode2]
-
- volume_pct = VolumeController.normalize_vol_pct(volume_pct)
# See: https://github.com/popcornmix/omxplayer/#volume-rw
+ volume_pct = VolumeController.normalize_vol_pct(volume_pct)
if volume_pct == 0:
volume_millibels = VolumeController.GLOBAL_MIN_VOL_VAL
else:
@@ -65,16 +59,16 @@ class ReceiverCommandBuilder:
f'{piwall2.receiver.receiver.Receiver.VIDEO_PLAYBACK_MBUFFER_SIZE_BYTES}b')
# See: https://github.com/dasl-/piwall2/blob/main/docs/configuring_omxplayer.adoc
- omx_cmd_template = ('omxplayer --crop {0} --adev {1} --display {2} --vol {3} --aspect-mode stretch ' +
- '--no-keys --timeout 30 --threshold 0.2 --video_fifo 35 --genlog pipe:0')
+ omx_cmd_template = ('omxplayer --adev {0} --display {1} --vol {2} --aspect-mode stretch ' +
+ '--no-keys --timeout 30 --threshold 0.2 --video_fifo 10 --genlog pipe:0')
omx_cmd = omx_cmd_template.format(
- shlex.quote(crop), shlex.quote(adev), shlex.quote(display), shlex.quote(str(volume_millibels))
+ shlex.quote(adev), shlex.quote(display), shlex.quote(str(volume_millibels))
)
cmd = 'set -o pipefail && '
if self.__receiver_config_stanza['is_dual_video_output']:
omx_cmd2 = omx_cmd_template.format(
- shlex.quote(crop2), shlex.quote(adev2), shlex.quote(display2), shlex.quote(str(volume_millibels))
+ shlex.quote(adev2), shlex.quote(display2), shlex.quote(str(volume_millibels))
)
cmd += f'{mbuffer_cmd} | tee >({omx_cmd}) >({omx_cmd2}) >/dev/null'
else:
@@ -82,51 +76,7 @@ class ReceiverCommandBuilder:
receiver_cmd = (f'{DirectoryUtils().root_dir}/bin/receive_and_play_video --command {shlex.quote(cmd)} ' +
f'--log-uuid {shlex.quote(log_uuid)}')
- return (receiver_cmd, crop_args, crop_args2)
-
- def __get_video_command_adev_args(self):
- receiver_config = self.__receiver_config_stanza
- adev = None
- if receiver_config['audio'] == 'hdmi' or receiver_config['audio'] == 'hdmi0':
- adev = 'hdmi'
- elif receiver_config['audio'] == 'headphone':
- adev = 'local'
- elif receiver_config['audio'] == 'hdmi_alsa' or receiver_config['audio'] == 'hdmi0_alsa':
- adev = 'alsa:default:CARD=b1'
- else:
- raise Exception(f"Unexpected audio config value: {receiver_config['audio']}")
-
- adev2 = None
- if receiver_config['is_dual_video_output']:
- if receiver_config['audio2'] == 'hdmi1':
- adev2 = 'hdmi1'
- elif receiver_config['audio2'] == 'headphone':
- adev2 = 'local'
- elif receiver_config['audio'] == 'hdmi1_alsa':
- adev2 = 'alsa:default:CARD=b2'
- else:
- raise Exception(f"Unexpected audio2 config value: {receiver_config['audio2']}")
-
- return (adev, adev2)
-
- def __get_video_command_display_args(self):
- receiver_config = self.__receiver_config_stanza
- display = None
- if receiver_config['video'] == 'hdmi' or receiver_config['video'] == 'hdmi0':
- display = '2'
- elif receiver_config['video'] == 'composite':
- display = '3'
- else:
- raise Exception(f"Unexpected video config value: {receiver_config['video']}")
-
- display2 = None
- if receiver_config['is_dual_video_output']:
- if receiver_config['video2'] == 'hdmi1':
- display2 = '7'
- else:
- raise Exception(f"Unexpected video2 config value: {receiver_config['video2']}")
-
- return (display, display2)
+ return receiver_cmd
"""
Returns a set of crop args supporting two display modes: tile mode and repeat mode.
@@ -136,7 +86,7 @@ class ReceiverCommandBuilder:
We return four crop settings because for each mode, we calculate the crop arguments
for each of two TVs (each receiver can have at most two TVs hooked up to it).
"""
- def __get_video_command_crop_args(self, video_width, video_height):
+ def get_crop_dimensions(self, video_width, video_height):
receiver_config = self.__receiver_config_stanza
#####################################################################################
@@ -230,6 +180,50 @@ class ReceiverCommandBuilder:
}
return (crop_args, crop_args2)
+ def __get_video_command_adev_args(self):
+ receiver_config = self.__receiver_config_stanza
+ adev = None
+ if receiver_config['audio'] == 'hdmi' or receiver_config['audio'] == 'hdmi0':
+ adev = 'hdmi'
+ elif receiver_config['audio'] == 'headphone':
+ adev = 'local'
+ elif receiver_config['audio'] == 'hdmi_alsa' or receiver_config['audio'] == 'hdmi0_alsa':
+ adev = 'alsa:default:CARD=b1'
+ else:
+ raise Exception(f"Unexpected audio config value: {receiver_config['audio']}")
+
+ adev2 = None
+ if receiver_config['is_dual_video_output']:
+ if receiver_config['audio2'] == 'hdmi1':
+ adev2 = 'hdmi1'
+ elif receiver_config['audio2'] == 'headphone':
+ adev2 = 'local'
+ elif receiver_config['audio'] == 'hdmi1_alsa':
+ adev2 = 'alsa:default:CARD=b2'
+ else:
+ raise Exception(f"Unexpected audio2 config value: {receiver_config['audio2']}")
+
+ return (adev, adev2)
+
+ def __get_video_command_display_args(self):
+ receiver_config = self.__receiver_config_stanza
+ display = None
+ if receiver_config['video'] == 'hdmi' or receiver_config['video'] == 'hdmi0':
+ display = '2'
+ elif receiver_config['video'] == 'composite':
+ display = '3'
+ else:
+ raise Exception(f"Unexpected video config value: {receiver_config['video']}")
+
+ display2 = None
+ if receiver_config['is_dual_video_output']:
+ if receiver_config['video2'] == 'hdmi1':
+ display2 = '7'
+ else:
+ raise Exception(f"Unexpected video2 config value: {receiver_config['video2']}")
+
+ return (display, display2)
+
"""
The displayable width and height represents the section of the video that the wall will be
displaying. A section of these dimensions will be taken from the center of the original
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment