diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 0a8876e6..19fe0a3f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -49,7 +49,8 @@ jobs: sudo apt-get update sudo apt-get install -y \ flawfinder squashfs-tools uuid-dev libuuid1 libffi-dev libssl-dev libssl1.1 \ - libarchive-dev libgpgme11-dev libseccomp-dev wget gcc make pkg-config + libarchive-dev libgpgme11-dev libseccomp-dev wget gcc make pkg-config \ + xvfb xdotool ffmpeg - name: Build and install Singularity run: | @@ -83,3 +84,39 @@ jobs: ./test_reprostim_container.sh --version cd ../.. pwd + + - name: Test timesync-stimuli run + run: | + export FRAME_WIDTH=1920 + export FRAME_HEIGHT=1080 + export FRAME_RATE=60 + export FRAME_BPP=24 + export DISPLAY_PATH="/tmp/reprostim_last_display.txt" + export XVFB_OPTS="-screen 0 ${FRAME_WIDTH}x${FRAME_HEIGHT}x${FRAME_BPP} -ac +extension GLX +render -noreset" + export DISPLAY_START=25 + export REPROSTIM_CMD="./run_reprostim_container.sh timesync-stimuli -m event --mute -d \$(cat /tmp/reprostim_last_display.txt)" + cd tools/ci + echo "Run Xvfb in background with REPROSTIM_CMD" + xvfb-run -a -n $DISPLAY_START -s "$XVFB_OPTS" bash -c "echo \$DISPLAY > ${DISPLAY_PATH}; $REPROSTIM_CMD"& + XVFB_RUN_PID=$! + echo "Started xvfb-run with PID $XVFB_RUN_PID" + echo "Wait for Xvfb to start" + sleep 4 + export DISPLAY=$(cat ${DISPLAY_PATH}) + echo "Xvfb started on display: $DISPLAY" + echo "Send test pulse events" + ./test_reprostim_events.sh 2 5 5 1.5 20 "${DISPLAY}" & + echo "Record video for 45 seconds" + ffmpeg -video_size "${FRAME_WIDTH}x${FRAME_HEIGHT}" -framerate "${FRAME_RATE}" -f x11grab -i "$DISPLAY" -t 45 -c:v libx264 -pix_fmt yuv420p "/tmp/reprostim_screenshot_$(date +%Y-%m-%d_%H-%M-%S).mp4" + sleep 45 + ls -l /tmp/reprostim_* + echo "Kill Xvfb at the end" + sleep 1 + kill $XVFB_RUN_PID 2>/dev/null || true + wait $XVFB_RUN_PID 2>/dev/null || true + + - name: Upload screenshot video artifact + uses: actions/upload-artifact@v4 + with: + name: reprostim-screenshot + path: /tmp/reprostim_screenshot*.mp4 diff --git a/docs/misc/xvfb-notes.md b/docs/misc/xvfb-notes.md new file mode 100644 index 00000000..0a432fdd --- /dev/null +++ b/docs/misc/xvfb-notes.md @@ -0,0 +1,103 @@ +# Virtual Screen Notes + +## Installation & Usage + +```shell +sudo apt update +sudo apt install xvfb +sudo apt install xdotool + +which xvfb-run +``` + +Run in background mode as FullHD: +```shell +# use FullHD resolution, disables access control, enables GLX and +# render extensions, no reset at last client exit +export XVFB_OPTS="-screen 0 1920x1080x24 -ac +extension GLX +render -noreset" + +# run on display :99 +Xvfb :99 -screen 0 1920x1080x24 & +export DISPLAY=:99 + +# or use automatic screen selection +Xvfb --auto-servernum --server-num=20 --auto-servernum --server-num=20 -s "$XVFB_OPTS" + +``` +Run say `xterm` in background: +```shell +xvfb-run xterm & +``` + +Make PNG screenshot: +```shell +import -display :99 -window root "screenshot_$(date +%Y-%m-%d_%H:%M:%S).png" +``` + +Kill Xvfb at the end: +```shell +killall Xvfb +``` + +## Script Example + +Now all together in a script: +```bash +# install Xvfb +sudo apt update +sudo apt install xvfb +sudo apt install xdotool +which xvfb-run + +# setup params +export FRAME_WIDTH=1920 +export FRAME_HEIGHT=1080 +export FRAME_RATE=60 +export FRAME_BPP=24 +export DISPLAY_PATH="/tmp/reprostim_last_display.txt" +export XVFB_OPTS="-screen 0 ${FRAME_WIDTH}x${FRAME_HEIGHT}x${FRAME_BPP} -ac +extension GLX +render -noreset" +export DISPLAY_START=25 +# export REPROSTIM_CMD="hatch run reprostim timesync-stimuli -m event --mute -d $(cat /tmp/reprostim_last_display.txt)" +export REPROSTIM_CMD="./run_reprostim_container.sh timesync-stimuli -m event --mute -d $(cat /tmp/reprostim_last_display.txt)" + +cd tools/ci + +# run Xvfb in background with REPROSTIM_CMD +xvfb-run -a -n $DISPLAY_START -s "$XVFB_OPTS" \ + bash -c 'echo $DISPLAY > ${DISPLAY_PATH}; $REPROSTIM_CMD'& + + +XVFB_RUN_PID=$! +echo "Started xvfb-run with PID $XVFB_RUN_PID" + +# wait for Xvfb to start +sleep 2 + +export DISPLAY=$(cat ${DISPLAY_PATH}) +echo "Xvfb started on display: $DISPLAY" + +# wait some time to start command +# sleep 5 + +# make screenshot +# import -display $DISPLAY -window root "/tmp/reprostim_screenshot${DISPLAY}_$(date +%Y-%m-%d_%H:%M:%S).png" + +# send test pulse events +./test_reprostim_events.sh 2 5 5 1.5 20& + +# record video for 45 seconds +ffmpeg -video_size ${FRAME_WIDTH}x${FRAME_HEIGHT} -framerate ${FRAME_RATE} -f x11grab -i $DISPLAY \ + -t 45 -c:v libx264 -pix_fmt yuv420p /tmp/reprostim_screenshot${DISPLAY}_$(date +%Y-%m-%d_%H:%M:%S).mp4 + +sleep 45 + +# kill Xvfb at the end +sleep 1 +kill $XVFB_RUN_PID +wait $XVFB_RUN_PID 2>/dev/null +``` + + + + + diff --git a/docs/source/notes/index.rst b/docs/source/notes/index.rst index 4a9ee51b..5dd81925 100644 --- a/docs/source/notes/index.rst +++ b/docs/source/notes/index.rst @@ -10,3 +10,4 @@ Miscellaneous Notes containers audiocodes-notes disp_mon-notes + xvfb-notes diff --git a/docs/source/notes/xvfb-notes.rst b/docs/source/notes/xvfb-notes.rst new file mode 100644 index 00000000..cbf7193b --- /dev/null +++ b/docs/source/notes/xvfb-notes.rst @@ -0,0 +1,2 @@ +.. include:: ../../misc/xvfb-notes.md + :parser: myst_parser.sphinx_ diff --git a/src/reprostim/cli/cmd_timesync_stimuli.py b/src/reprostim/cli/cmd_timesync_stimuli.py index cef61aff..aebb29f4 100644 --- a/src/reprostim/cli/cmd_timesync_stimuli.py +++ b/src/reprostim/cli/cmd_timesync_stimuli.py @@ -52,9 +52,10 @@ @click.option( "-d", "--display", - default=1, - type=int, - help="Specify display number as an integer (default: 1).", + default="1", + type=str, + help="Specify display number as an integer or as X11 format like " + ":display_num (default: 1).", ) @click.option( "-s", @@ -111,7 +112,7 @@ "-t", "--trials", default=300, type=int, help="Specifies number of trials." ) @click.option( - "-d", + "-x", "--duration", default=-1, type=float, @@ -140,7 +141,7 @@ def timesync_stimuli( output_prefix: str, windowed: bool, win_size: tuple[int, int], - display: int, + display: str, qr_scale: float, qr_duration: float, qr_async: bool, @@ -177,6 +178,8 @@ def timesync_stimuli( logger.debug(f" qr async : {qr_async}") logger.debug(f" interval : {interval}") + display_num: int = int(display.lstrip(':')) + output: str = get_output_file_name(output_prefix, start_ts) logger.debug(f" output : {output}") @@ -192,7 +195,7 @@ def timesync_stimuli( output, is_fullscreen, win_size, - display, + display_num, qr_scale, qr_duration, qr_async, diff --git a/src/reprostim/qr/timesync_stimuli.py b/src/reprostim/qr/timesync_stimuli.py index 75262d6c..1ee9369f 100644 --- a/src/reprostim/qr/timesync_stimuli.py +++ b/src/reprostim/qr/timesync_stimuli.py @@ -484,9 +484,10 @@ def wait_or_keys( if win.monitor: logger.info(f"display [{display}] info:") + fr = win.getActualFrameRate() logger.info( f" {win.size[0]}x{win.size[1]} px, " - f"{round(win.getActualFrameRate(), 2)} Hz" + f" {round(fr, 2)} Hz" if fr else " N/A Hz" ) # log script started event diff --git a/tools/ci/test_reprostim_events.sh b/tools/ci/test_reprostim_events.sh new file mode 100755 index 00000000..2b88bc90 --- /dev/null +++ b/tools/ci/test_reprostim_events.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Script to simulate pulse events using xdotool +# Usage: ./test_reprostim_events.sh NUM_SERIES SERIES_INTERVAL NUM_EVT EVT_INTERVAL [SLEEP_DELAY] [DISPLAY] +# Make sure to set DISPLAY if using Xvfb + +NUM_SERIES="$1" +SERIES_INTERVAL="$2" +NUM_EVT="$3" +EVT_INTERVAL="$4" +SLEEP_DELAY="${5:-0}" +DISPLAY_PARAM="$6" + +# Use passed-in DISPLAY if provided +if [[ -n "$DISPLAY_PARAM" ]]; then + export DISPLAY="$DISPLAY_PARAM" +fi + +echo "DISPLAY is set to: $DISPLAY" + +sleep "$SLEEP_DELAY" + +# Validate inputs +if [ -z "$NUM_SERIES" ] || [ -z "$SERIES_INTERVAL" ] || [ -z "$NUM_EVT" ] || [ -z "$EVT_INTERVAL" ]; then + echo "Usage: $0 [] []" + exit 1 +fi + +start_time=$(date +%s) + +for (( series=1; series<=NUM_SERIES; series++ )); do + echo "Starting series $series" + for (( event=1; event<=NUM_EVT; event++ )); do + echo "Sending pulse event $series.$event" + xdotool key 5 + sleep "$EVT_INTERVAL" + done + + if [ "$series" -lt "$NUM_SERIES" ]; then + echo "Waiting $SERIES_INTERVAL seconds before next series..." + sleep "$SERIES_INTERVAL" + fi +done + +# Escape and quit the application +echo "Finishing up and send ESC q ESC..." +xdotool key Escape +sleep 0.2 +xdotool key q +sleep 0.2 +xdotool key Escape + +end_time=$(date +%s) +dt=$(( end_time - start_time )) + +echo "Done. Execution time: $dt seconds"