From 3621b340e18aec95fa86704d664603cc9435fb70 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Mon, 12 May 2025 05:57:10 +0000 Subject: [PATCH 1/8] Add FFmpeg based instruction to measurement E2E latency --- docs/FFmpegPlugin.md | 11 ++- docs/LatencyMeasurement.md | 159 +++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 docs/LatencyMeasurement.md diff --git a/docs/FFmpegPlugin.md b/docs/FFmpegPlugin.md index 8b33d53fa..81a3b6570 100644 --- a/docs/FFmpegPlugin.md +++ b/docs/FFmpegPlugin.md @@ -23,7 +23,11 @@ replace `7.0` with `6.1` in the following script. 1. Run the FFmpeg configuration tool ```bash - ./configure-ffmpeg.sh + ./configure-ffmpeg.sh + ``` + To be able to measure end-to-end please use following configuration parameters + ```bash + ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig ``` 1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin @@ -275,6 +279,11 @@ ffmpeg version n6.1.1-152-ge821e6c21d Copyright (c) 2000-2023 the FFmpeg develop built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04) ``` +While measuring latency, it is necessary to configure FFmpeg with additional parameters. If the error `No such filter: 'drawtext'` occurs, please reconfigure and rebuild FFmpeg: + ```bash + ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ``` + [license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg [license]: https://opensource.org/license/bsd-3-clause diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md new file mode 100644 index 000000000..3b751267a --- /dev/null +++ b/docs/LatencyMeasurement.md @@ -0,0 +1,159 @@ +# End To End Latency Measurement — Media Communications Mesh + +Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to familiarize yourself with FFmpeg usage, as this document provides an extension. + +**Note:** Currently, latency measurement is available only for video transport. + +## Build and install steps + +1. Please follow build and install steps from [FFmpeg Plugin](FFmpegPlugin.md). with one exception, FFmpeg configuration tool requires additional parameters. + ```bash + ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ``` +## Example – Run video transmission between 2 FFmpeg instances on the same host + +This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh on the same host, with timestamps to measure end-to-end latency using the `drawtext` feature in FFmpeg. The `drawtext` filter allows you to overlay timestamps directly onto the video stream, providing a visual representation of latency as the video is processed and transmitted between instances/hosts. + +### Run Mesh Agent + ```bash + mesh-agent + ``` + +### Run Media Proxy + + ```bash + sudo media_proxy \ + -d 0000:32:01.1 \ + -i 192.168.96.11 \ + -r 192.168.97.11 \ + -p 9200-9299 \ + -t 8002 + ``` + +### Receiver side setup + + ```bash + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ + -f mcm \ + -conn_type multipoint-group \ + -frame_rate 60 \ + -video_size 1920x1080 \ + -pixel_format yuv422p10le \ + -i - \ + -vf \ + "drawtext=fontsize=50: \ + text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ + x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ + -vcodec mpeg4 -qscale:v 3 recv.mp4 + ``` +### Sender side setup + + ```bash + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg -i \ + -vf \ + "drawtext=fontsize=50: \ + text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ + x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ + -f mcm \ + -conn_type multipoint-group \ + -frame_rate 60 \ + -video_size 1920x1080 \ + -pixel_format yuv422p10le - + ``` + + When sending a raw video file that lack metadata, you must explicitly provide FFmpeg + with the necessary video frame details. This includes specifying the format + `-f rawvideo`, pixel format `-pix_fmt`, and resolution `-s WxH`. Example: + + ```bash + ffmpeg -f rawvideo -pix_fmt yuv422p10le -s 1920x1080 -i ... + ``` + To get meaningfull latency measurement it is also recommended to provide rate `-readrate` at which FFmpeg will read frames from file . Example: + ```bash + ffmpeg -f rawvideo -readrate 2.4 -pix_fmt yuv422p10le -s 1920x1080 -i ... + ``` + Please note that `-readrate` value have to be calculated based on `-frame_rate` parameter, simple equation is as follow: + `readrate = frame_rate / 25`, for example: + | frame_rate | readrate | + |------------|-------------------| + | 25 | 25 / 25 = 1 | + | 50 | 50 / 25 = 2 | + | 60 | 60 / 25 = 2.4 | + + +## Example – Run video transmission between 2 FFmpeg instances on different hosts + +This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh, with timestamps to measure end-to-end latency using the `drawtext` feature in FFmpeg. The `drawtext` filter allows you to overlay timestamps directly onto the video stream, providing a visual representation of latency as the video is processed and transmitted between instances/hosts. + +### Run Mesh Agent + ```bash + mesh-agent + ``` + +### Receiver side setup + +1. Start Media Proxy + + ```bash + sudo media_proxy \ + -d 0000:32:01.1 \ + -i 192.168.96.11 \ + -r 192.168.97.11 \ + -p 9200-9299 \ + -t 8002 + ``` + +1. Start FFmpeg to receive frames from Media Communications Mesh and save received data on hard drive + + ```bash + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ + -f mcm \ + -conn_type st2110 \ + -transport st2110-20 \ + -ip_addr 192.168.96.10 \ + -port 9001 \ + -frame_rate 60 \ + -video_size 1920x1080 \ + -pixel_format yuv422p10le \ + -i - \ + -vf \ + "drawtext=fontsize=50: \ + text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ + x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ + -vcodec mpeg4 -qscale:v 3 recv.mp4 + ``` + +### Sender side setup + +1. Start Media Proxy + + ```bash + sudo media_proxy \ + -d 0000:32:01.0 \ + -i 192.168.96.10 \ + -r 192.168.97.10 \ + -p 9100-9199 \ + -t 8001 + ``` + +2. Start FFmpeg to stream a video file to the receiver via Media Communications Mesh + + ```bash + sudo MCM_MEDIA_PROXY_PORT=8001 ffmpeg -i \ + -vf \ + "drawtext=fontsize=50: \ + text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ + x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ + -f mcm \ + -conn_type st2110 \ + -transport st2110-20 \ + -ip_addr 192.168.96.11 \ + -port 9001 \ + -frame_rate 60 \ + -video_size 1920x1080 \ + -pixel_format yuv422p10le - + ``` + + +[license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg +[license]: https://opensource.org/license/bsd-3-clause From 99e5c2962cff746caa2e46236459febdd0493e02 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Mon, 12 May 2025 12:24:19 +0000 Subject: [PATCH 2/8] docs: Add stream postprocessing instructions and Python script for latency analysis --- docs/LatencyMeasurement.md | 34 +++++++++++ scripts/text-detection.py | 115 +++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 scripts/text-detection.py diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md index 3b751267a..6e6aea875 100644 --- a/docs/LatencyMeasurement.md +++ b/docs/LatencyMeasurement.md @@ -10,6 +10,20 @@ Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to fa ```bash ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig ``` +## Time synchronization between hosts + +__host-1 Controller clock__ +```bash +sudo ptp4l -i -m 2 +``` +__host-2 Worker clock__ +```bash +sudo ptp4l -i -m 2 -s +sudo -s phc2sys -s -c CLOCK_REALTIME -O 0 -m +``` +*Please note that `network_interface_1` and `network_interface_2` have to be physically connected to the same network + + ## Example – Run video transmission between 2 FFmpeg instances on the same host This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh on the same host, with timestamps to measure end-to-end latency using the `drawtext` feature in FFmpeg. The `drawtext` filter allows you to overlay timestamps directly onto the video stream, providing a visual representation of latency as the video is processed and transmitted between instances/hosts. @@ -154,6 +168,26 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -pixel_format yuv422p10le - ``` +## Stream postprocessing +The generated stream can be analyzed manually, but it is a long process. To accelerate it, there is a small sript written in Python that automatically extracts and plots latency. + +1. install reuired python packages + ```bash + apt install tesseract-ocr + pip install opencv-python + pip install pytesseract + pip install matplotlib + ``` +2. Postprocess stream with command + ```bash + python text-detection.py + ``` + ```bash + python text-detection.py recv.mp4 latency_chart.jpg + ``` +When preparing FFmpeg command if you change parameters of `drawtext` filter, especialy `fontsize`, `x`, `y` or `text`, you have to adjust script __text-detection.py__ too, please refer to function `extract_text_from_region(image, x, y, font_size, length)` + + [license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg [license]: https://opensource.org/license/bsd-3-clause diff --git a/scripts/text-detection.py b/scripts/text-detection.py new file mode 100644 index 000000000..0f98a3755 --- /dev/null +++ b/scripts/text-detection.py @@ -0,0 +1,115 @@ +import sys +import pytesseract +import cv2 as cv +import numpy as np +from datetime import datetime +import re +import matplotlib.pyplot as plt +from concurrent.futures import ThreadPoolExecutor +import os + +def is_display_attached(): + # Check if the DISPLAY environment variable is set + return 'DISPLAY' in os.environ + +def extract_text_from_region(image, x, y, font_size, length): + """ + Extracts text from a specific region of the image. + :param image: The image to extract text from. + :param x: The x-coordinate of the top-left corner of the region. + :param y: The y-coordinate of the top-left corner of the region. + :param font_size: The font size of the text. + :param length: The length of the text to extract. + :return: The extracted text. + """ + margin = 5 + y_adjusted = max(0, y - margin) + x_adjusted = max(0, x - margin) + height = y + font_size + margin + width = x + length + margin + # Define the region of interest (ROI) for text extraction + roi = image[y_adjusted:height, x_adjusted:width] + + # Use Tesseract to extract text from the ROI + return pytesseract.image_to_string(roi, lang='eng') + +def process_frame(frame_idx, frame): + print("Processing Frame: ", frame_idx) + + timestamp_format = "%H:%M:%S:%f" + timestamp_pattern = r'\b\d{2}:\d{2}:\d{2}:\d{3}\b' + + # Convert frame to grayscale for better OCR performance + frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) + + line_1 = extract_text_from_region(frame, 10, 10, 50, 700) + line_2 = extract_text_from_region(frame, 10, 70, 50, 700) + + # Find the timestamps(Type: string) in the extracted text using regex + tx_time = re.search(timestamp_pattern, line_1) + rx_time = re.search(timestamp_pattern, line_2) + + if tx_time is None or rx_time is None: + print("Error: Timestamp not found in the expected format.") + return 0 + + # Convert the timestamps(Type: string) to time (Type: datetime) + tx_time = datetime.strptime(tx_time.group(), timestamp_format) + rx_time = datetime.strptime(rx_time.group(), timestamp_format) + + if tx_time is None or rx_time is None: + print("Error: Timestamp not found in the expected format.") + return 0 + + if tx_time > rx_time: + print("Error: Transmit time is greater than receive time.") + return 0 + + time_difference = rx_time - tx_time + time_difference_ms = time_difference.total_seconds() * 1000 + return time_difference_ms + +def main(): + if len(sys.argv) < 2: + print("Usage: python text-detection.py ") + sys.exit(1) + + cap = cv.VideoCapture(sys.argv[1]) + if not cap.isOpened(): + print("Error: Could not open video file.") + sys.exit(1) + + frame_idx = 0 + time_differences = [] + + with ThreadPoolExecutor(max_workers=40) as executor: + futures = [] + while True: + ret, frame = cap.read() + if not ret: + break + + futures.append(executor.submit(process_frame, frame_idx, frame)) + frame_idx += 1 + + for future in futures: + time_differences.append(future.result()) + + cap.release() + + plt.plot(time_differences, marker='o') + plt.title('Media Communication Mesh Latency') + plt.xlabel('Frame Index') + plt.ylabel('Measured latency (ms)') + plt.grid(True) + if is_display_attached(): + plt.show() + + if len(sys.argv) == 3: + filename = sys.argv[2] + if not filename.endswith('.jpg'): + filename += '.jpg' + print("Saving the plot to: ", filename) + plt.savefig(filename, format='jpg', dpi=300) + +main() From 67059d5f93c8b91605d2b3e3a27d9b4ee48d5864 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Wed, 14 May 2025 05:58:59 +0000 Subject: [PATCH 3/8] Address multiple github suggestions --- docs/FFmpegPlugin.md | 6 +++--- docs/LatencyMeasurement.md | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/FFmpegPlugin.md b/docs/FFmpegPlugin.md index 81a3b6570..c283d2e4d 100644 --- a/docs/FFmpegPlugin.md +++ b/docs/FFmpegPlugin.md @@ -25,9 +25,9 @@ replace `7.0` with `6.1` in the following script. ```bash ./configure-ffmpeg.sh ``` - To be able to measure end-to-end please use following configuration parameters + To be able to measure end-to-end latency please use following configuration parameters ```bash - ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig ``` 1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin @@ -281,7 +281,7 @@ ffmpeg version n6.1.1-152-ge821e6c21d Copyright (c) 2000-2023 the FFmpeg develop While measuring latency, it is necessary to configure FFmpeg with additional parameters. If the error `No such filter: 'drawtext'` occurs, please reconfigure and rebuild FFmpeg: ```bash - ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig ``` diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md index 6e6aea875..2edeb11f1 100644 --- a/docs/LatencyMeasurement.md +++ b/docs/LatencyMeasurement.md @@ -8,7 +8,7 @@ Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to fa 1. Please follow build and install steps from [FFmpeg Plugin](FFmpegPlugin.md). with one exception, FFmpeg configuration tool requires additional parameters. ```bash - ./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig ``` ## Time synchronization between hosts @@ -82,7 +82,7 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t ```bash ffmpeg -f rawvideo -pix_fmt yuv422p10le -s 1920x1080 -i ... ``` - To get meaningfull latency measurement it is also recommended to provide rate `-readrate` at which FFmpeg will read frames from file . Example: + To get meaningful latency measurement it is also recommended to provide rate `-readrate` at which FFmpeg will read frames from file. Example: ```bash ffmpeg -f rawvideo -readrate 2.4 -pix_fmt yuv422p10le -s 1920x1080 -i ... ``` @@ -171,9 +171,12 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t ## Stream postprocessing The generated stream can be analyzed manually, but it is a long process. To accelerate it, there is a small sript written in Python that automatically extracts and plots latency. -1. install reuired python packages +1. install Install Tesseract OCR ```bash apt install tesseract-ocr + ``` +2. Install Python packages + ```bash pip install opencv-python pip install pytesseract pip install matplotlib From f7bd1da792cdf193ea159e81290c4db637b5e0d8 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Wed, 14 May 2025 12:24:57 +0000 Subject: [PATCH 4/8] Doc: Specify python componet versions --- docs/LatencyMeasurement.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md index 2edeb11f1..92a7be222 100644 --- a/docs/LatencyMeasurement.md +++ b/docs/LatencyMeasurement.md @@ -15,11 +15,12 @@ Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to fa __host-1 Controller clock__ ```bash sudo ptp4l -i -m 2 +sudo phc2sys -a -r -r -m ``` __host-2 Worker clock__ ```bash sudo ptp4l -i -m 2 -s -sudo -s phc2sys -s -c CLOCK_REALTIME -O 0 -m +sudo phc2sys -a -r ``` *Please note that `network_interface_1` and `network_interface_2` have to be physically connected to the same network @@ -177,16 +178,14 @@ The generated stream can be analyzed manually, but it is a long process. To acce ``` 2. Install Python packages ```bash - pip install opencv-python - pip install pytesseract - pip install matplotlib + pip install opencv-python~=4.11.0 pytesseract~=0.3.13 matplotlib~=3.10.3 ``` 2. Postprocess stream with command ```bash - python text-detection.py + python text-detection.py ``` ```bash - python text-detection.py recv.mp4 latency_chart.jpg + python text-detection.py recv.mp4 latency.jpg ``` When preparing FFmpeg command if you change parameters of `drawtext` filter, especialy `fontsize`, `x`, `y` or `text`, you have to adjust script __text-detection.py__ too, please refer to function `extract_text_from_region(image, x, y, font_size, length)` From e45defad420801927a9c6e718cd4b489e1305962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Grabuszy=C5=84ski?= Date: Mon, 19 May 2025 14:29:12 +0200 Subject: [PATCH 5/8] [typo] Update scripts/text-detection.py plot title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Konstantin Ilichev <2067613@gmail.com> Signed-off-by: Mateusz Grabuszyński --- scripts/text-detection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/text-detection.py b/scripts/text-detection.py index 0f98a3755..47effc0b6 100644 --- a/scripts/text-detection.py +++ b/scripts/text-detection.py @@ -98,7 +98,7 @@ def main(): cap.release() plt.plot(time_differences, marker='o') - plt.title('Media Communication Mesh Latency') + plt.title('Media Communications Mesh Latency') plt.xlabel('Frame Index') plt.ylabel('Measured latency (ms)') plt.grid(True) From 063e257ab2b90d25968d140f3658bcc3452cf69d Mon Sep 17 00:00:00 2001 From: Konstantin Ilichev Date: Mon, 2 Jun 2025 18:40:07 +0000 Subject: [PATCH 6/8] Improve latency solution report and rework documentation * Make the font size a bit smaller. * Avoid taking zero delta values. * Filter out anomaly peaks exceeding the average value by 25% from the calculation of the average latency. * Add file info to the diagram. * Print results in console. * Rework the latency measurement solution documentation. Signed-off-by: Konstantin Ilichev --- docs/FFmpegPlugin.md | 9 - docs/LatencyMeasurement.md | 215 +++++++++++++----- .../ffmpeg-based-latency-solution-diagram.jpg | Bin 0 -> 143786 bytes scripts/text-detection.py | 66 +++++- 4 files changed, 215 insertions(+), 75 deletions(-) create mode 100644 docs/_static/ffmpeg-based-latency-solution-diagram.jpg diff --git a/docs/FFmpegPlugin.md b/docs/FFmpegPlugin.md index c283d2e4d..b687cd456 100644 --- a/docs/FFmpegPlugin.md +++ b/docs/FFmpegPlugin.md @@ -25,10 +25,6 @@ replace `7.0` with `6.1` in the following script. ```bash ./configure-ffmpeg.sh ``` - To be able to measure end-to-end latency please use following configuration parameters - ```bash - ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig - ``` 1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin @@ -279,11 +275,6 @@ ffmpeg version n6.1.1-152-ge821e6c21d Copyright (c) 2000-2023 the FFmpeg develop built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04) ``` -While measuring latency, it is necessary to configure FFmpeg with additional parameters. If the error `No such filter: 'drawtext'` occurs, please reconfigure and rebuild FFmpeg: - ```bash - ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig - ``` - [license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg [license]: https://opensource.org/license/bsd-3-clause diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md index 92a7be222..4a10c1b23 100644 --- a/docs/LatencyMeasurement.md +++ b/docs/LatencyMeasurement.md @@ -1,40 +1,116 @@ -# End To End Latency Measurement — Media Communications Mesh +# End-to-End Latency Measurement — Media Communications Mesh + +This document describes a simple solution for measuring end-to-end latency in Media Communications Mesh. + +## Overview + +The solution is based on the FFmpeg ability to print current timestamps on the sender side (Tx) and the receiver side (Rx), and the use of Optical Character Recognition (OCR) to read the timestamps out of each received video frame and calculate the delta. The choice of OCR is determined by the fact that the text can be effectively recognized even if the picture is affected by any sort of a lossy video compression algorithm somewhere in the transmission path in the Mesh. To achieve proper accuracy of the measurement, both Tx and Rx host machines should be synchronized using Precision Time Protocol (PTP). + +> Only video payload is supported. + +```mermaid +flowchart LR + tx-file((Input + video file)) + tx-ffmpeg(Tx + FFmpeg) + subgraph mesh [Media Communications Mesh] + direction LR + proxy1(Proxy1) + proxy2a(. . .) + proxy2b(. . .) + proxy2c(. . .) + proxy3(ProxyN) + proxy1 --> proxy2a --> proxy3 + proxy1 --> proxy2b --> proxy3 + proxy1 --> proxy2c --> proxy3 + end + rx-ffmpeg(Rx + FFmpeg) + rx-file((Output + video file)) + + tx-file --> tx-ffmpeg --> mesh --> rx-ffmpeg --> rx-file +``` + +## How it works + +1. Tx side – The user starts FFmpeg with special configuration to stream video via the Mesh. +1. Rx side – The user starts FFmpeg with special configuration to receive the video stream from the Mesh. +1. Tx side – FFmpeg prints the current timestamp as a huge text at the top of each video frame and transmits it via the Mesh. +1. Rx side – FFmpeg prints the current timestamp as a huge text at the bottom of each video frame received from the Mesh and saves it on the disk. +1. After transmission is done, there is a resulting MPEG video file on the disk on the Rx side. +1. The user runs the solution script against the MPEG file that recognizes the Tx and Rx timestamps in each frame, and calculates the average latency based on the difference between the timestamps. Additionally, the script generates a latency diagram and stores it in JPEG format on the disk. + +## Sample latency diagram + + + +## Important notice on latency measurement results -Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to familiarize yourself with FFmpeg usage, as this document provides an extension. +> Please note the calculated average latency is highly dependent on the hardware configuration and CPU background load, and cannot be treated as an absolute value. The provided solution can only be used for comparing the latency in different Mesh configurations and video streaming parameters, as well as latency stability checks. -**Note:** Currently, latency measurement is available only for video transport. ## Build and install steps -1. Please follow build and install steps from [FFmpeg Plugin](FFmpegPlugin.md). with one exception, FFmpeg configuration tool requires additional parameters. +> It is assumed that Media Communications Mesh is installed on the Tx and Rx host machines according to [Setup Guide](SetupGuid.md). + +If [FFmpeg Plugin](FFmpegPlugin.md) was installed earlier, remove its directory before proceeding with the following. + +1. Clone the FFmpeg 7.0 repository and apply patches. + ```bash - ./configure-ffmpeg.sh ${FFMPEG_VER} --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ./clone-and-patch-ffmpeg.sh ``` -## Time synchronization between hosts -__host-1 Controller clock__ -```bash -sudo ptp4l -i -m 2 -sudo phc2sys -a -r -r -m -``` -__host-2 Worker clock__ -```bash -sudo ptp4l -i -m 2 -s -sudo phc2sys -a -r -``` -*Please note that `network_interface_1` and `network_interface_2` have to be physically connected to the same network +1. Run the FFmpeg configuration tool with special features enabled + + ```bash + ./configure-ffmpeg.sh 7.0 --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig + ``` + +1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin + + ```bash + ./build-ffmpeg.sh + ``` + +1. Install Tesseract OCR + ```bash + apt install tesseract-ocr + ``` +1. Install Python packages + ```bash + pip install opencv-python~=4.11.0 pytesseract~=0.3.13 matplotlib~=3.10.3 + ``` + +1. Setup time synchronization on host machines + + > Make sure `network_interface_1` and `network_interface_2` are connected to the same network. + * __host-1 Controller clock__ + ```bash + sudo ptp4l -i -m 2 + sudo phc2sys -a -r -r -m + ``` -## Example – Run video transmission between 2 FFmpeg instances on the same host + * __host-2 Worker clock__ + ```bash + sudo ptp4l -i -m 2 -s + sudo phc2sys -a -r + ``` -This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh on the same host, with timestamps to measure end-to-end latency using the `drawtext` feature in FFmpeg. The `drawtext` filter allows you to overlay timestamps directly onto the video stream, providing a visual representation of latency as the video is processed and transmitted between instances/hosts. +## Example – Measuring transmission latency between two FFmpeg instances on the same host -### Run Mesh Agent +This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh on the same host, and then calculation of transmission latency from the recorded video. + + +1. Run Mesh Agent ```bash mesh-agent ``` -### Run Media Proxy +1. Run Media Proxy ```bash sudo media_proxy \ @@ -45,7 +121,7 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -t 8002 ``` -### Receiver side setup +1. Start the Receiver side FFmpeg instance ```bash sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ @@ -56,17 +132,17 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -pixel_format yuv422p10le \ -i - \ -vf \ - "drawtext=fontsize=50: \ + "drawtext=fontsize=40: \ text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ -vcodec mpeg4 -qscale:v 3 recv.mp4 ``` -### Sender side setup +1. Start the Sender side FFmpeg instance ```bash sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg -i \ -vf \ - "drawtext=fontsize=50: \ + "drawtext=fontsize=40: \ text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ -f mcm \ @@ -76,38 +152,60 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -pixel_format yuv422p10le - ``` - When sending a raw video file that lack metadata, you must explicitly provide FFmpeg - with the necessary video frame details. This includes specifying the format - `-f rawvideo`, pixel format `-pix_fmt`, and resolution `-s WxH`. Example: + When sending a raw video file, e.g. of the YUV format, you have to explicitly specify the file format `-f rawvideo`, the pixel format `-pix_fmt`, and the video resolution `-s WxH`: ```bash ffmpeg -f rawvideo -pix_fmt yuv422p10le -s 1920x1080 -i ... ``` - To get meaningful latency measurement it is also recommended to provide rate `-readrate` at which FFmpeg will read frames from file. Example: + + It is also recommended to provide the read rate `-readrate` at which FFmpeg will read frames from the file: + ```bash ffmpeg -f rawvideo -readrate 2.4 -pix_fmt yuv422p10le -s 1920x1080 -i ... ``` - Please note that `-readrate` value have to be calculated based on `-frame_rate` parameter, simple equation is as follow: - `readrate = frame_rate / 25`, for example: + + The `-readrate` value is calculated from the `-frame_rate` parameter value using the following equation: $readrate=framerate\div25$. Use the pre-calculated values from the table below. + | frame_rate | readrate | |------------|-------------------| | 25 | 25 / 25 = 1 | | 50 | 50 / 25 = 2 | | 60 | 60 / 25 = 2.4 | +1. Run the script against the recorded MPEG file. The first argument is the input video file path. The second argument is the optional latency diagram JPEG file path to be generated. + + ```bash + python text-detection.py recv.mp4 recv-latency.jpg + ``` + + Console output + ```bash + ... + Processing Frame: 235 + Processing Frame: 236 + Processing Frame: 237 + Processing Frame: 238 + Processing Frame: 239 + Processing Frame: 240 + Saving the latency chart to: recv-latency.jpg + File: recv.mp4 | Last modified: 2025-06-02 13:49:54 UTC + Resolution: 640x360 | FPS: 25.00 + Average End-to-End Latency: 564.61 ms + ``` + + See the [Sample latency diagram](#sample-latency-diagram). + -## Example – Run video transmission between 2 FFmpeg instances on different hosts +## Example – Measuring transmission latency between two FFmpeg instances on different hosts -This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh, with timestamps to measure end-to-end latency using the `drawtext` feature in FFmpeg. The `drawtext` filter allows you to overlay timestamps directly onto the video stream, providing a visual representation of latency as the video is processed and transmitted between instances/hosts. +This example demonstrates sending a video file from the 1st FFmpeg instance to the 2nd FFmpeg instance via Media Communications Mesh on the same host, and then calculation of transmission latency from the recorded video. -### Run Mesh Agent +1. Run Mesh Agent ```bash mesh-agent ``` -### Receiver side setup - -1. Start Media Proxy +1. Start Media Proxy on the Receiver host machine ```bash sudo media_proxy \ @@ -118,7 +216,7 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -t 8002 ``` -1. Start FFmpeg to receive frames from Media Communications Mesh and save received data on hard drive +1. Start the Receiver side FFmpeg instance ```bash sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ @@ -132,15 +230,13 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -pixel_format yuv422p10le \ -i - \ -vf \ - "drawtext=fontsize=50: \ + "drawtext=fontsize=40: \ text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ -vcodec mpeg4 -qscale:v 3 recv.mp4 ``` -### Sender side setup - -1. Start Media Proxy +1. Start Media Proxy on the Sender host machine ```bash sudo media_proxy \ @@ -151,12 +247,12 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -t 8001 ``` -2. Start FFmpeg to stream a video file to the receiver via Media Communications Mesh +1. Start the Sender side FFmpeg instance ```bash sudo MCM_MEDIA_PROXY_PORT=8001 ffmpeg -i \ -vf \ - "drawtext=fontsize=50: \ + "drawtext=fontsize=40: \ text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ -f mcm \ @@ -169,25 +265,28 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t -pixel_format yuv422p10le - ``` -## Stream postprocessing -The generated stream can be analyzed manually, but it is a long process. To accelerate it, there is a small sript written in Python that automatically extracts and plots latency. +1. Run the script against the recorded MPEG file. The first argument is the input video file path. The second argument is the optional latency diagram JPEG file path to be generated. -1. install Install Tesseract OCR ```bash - apt install tesseract-ocr - ``` -2. Install Python packages - ```bash - pip install opencv-python~=4.11.0 pytesseract~=0.3.13 matplotlib~=3.10.3 - ``` -2. Postprocess stream with command - ```bash - python text-detection.py + python text-detection.py recv.mp4 recv-latency.jpg ``` + + Console output ```bash - python text-detection.py recv.mp4 latency.jpg + ... + Processing Frame: 235 + Processing Frame: 236 + Processing Frame: 237 + Processing Frame: 238 + Processing Frame: 239 + Processing Frame: 240 + Saving the latency chart to: recv-latency.jpg + File: recv.mp4 | Last modified: 2025-06-02 13:49:54 UTC + Resolution: 640x360 | FPS: 25.00 + Average End-to-End Latency: 564.61 ms ``` -When preparing FFmpeg command if you change parameters of `drawtext` filter, especialy `fontsize`, `x`, `y` or `text`, you have to adjust script __text-detection.py__ too, please refer to function `extract_text_from_region(image, x, y, font_size, length)` + + See the [Sample latency diagram](#sample-latency-diagram). diff --git a/docs/_static/ffmpeg-based-latency-solution-diagram.jpg b/docs/_static/ffmpeg-based-latency-solution-diagram.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ccee5a89d2f85907482558b56470d53fa8d4fde4 GIT binary patch literal 143786 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℞RaY-gguXtR=#U}38Q)RuNog`0n@1sVh~3Ud#00qjl+*=) zlG+JKdC*n0^~i#exfKkRT}j^YGKQ-MB1v>`_DH&S>*)AdibLSt+FTzB6$aDTW#+LX zsW{(sW*|5F&jj9fYpa1mY#zt%%9KMlcB7;j;rY>eS2Qq|Z0BGB6=#!2TDN1^ZowY~ z&^+Aakw7d3k$zhd3-ccHnWz4>YKm&>r@3lv ze*Y!kg|4!lL}g5Lj%ibdc7}b5;4MN5aMMk}p#OjXJ6)pz2nm{JlPGbbtQ5;nNp=qB zuPunSxNAQ{8hj|qHR#d0BQvE|#;Q@a&&Rf<8Y`({8#*9$C(Z4~{-`D+X*P2^wFjo>5z?5TMs(zw*lgu0MC92elQb9EG z=pkHzic`v$omO!vNWI?laka~XwO!Xa6%)whyPLkqi&}OSI~x$P*8QIy++}fYlUW}3 zcbiikXjJX<>znP-izdluUI=vFP;{&xe1AJ^j=c9wq}2S^W=h-<%3%_SiW1dsoD>(J zadKnn;o=L{mju9CKlYysJ*1207M_D0LlA)HUJ&gW=-THX*gVE$b+(H{e);kh&Ecve z0hR~TcGbLh09>R5c3u2~^a5RQY=cgaroM9;ax3(!k$5@zdEYjF;)8QXAO{%o@59Es z%prZbRL?A7xXP;Z;!!7hyk&8Z%w%nuenJzsVS)!1T?z8a0i={ka%w<@26d2PP4Pg? zsnx-4q$sHcO^QosD^aUWxj!boNRz>3=4_nr@hVlUl&n9%-5k;jtXb%|Ux3xn>iaC) zL-h~MP7ysVb>xG=7UiBNdVsdBcsBe;$ALE&{M8s=BvImd1&24+=GOAtwsfxN%LkPk@>bj!?p(P}I!Rmd>pEq*U0U1Nlc($#kI21yImV$QYI8kEyi!C!TNbN@Kpcluf=WW zZ;yQ+iZvegj}LP*a%y#fae0jJ^=rk{CAbbXB+n&Eh(nUqO%;PbN#DQ+Gbnt68r^8uP{(0j#q z18<)a5U>#+yl`mgIHPzWmKEK|_cUGT*)Nd`>j4LP`ee(2|3Yv88R<8E--fe=%!($S zbMpG5LXYTBbOgcs<8DQfI$4kHd|tl3qf_@n3!HycWz_>`l5yeQ*lOMwj*Y!m-JxwH zjP;cY`w}Ndp6B&wEEA*$RLQ-4_>T<;uu7@o21wfJQ6WEuuIFBYi2|ZwswKw-uSUH2 zkeDmidd4H#d045?QKcT(@q~{L7o1vTfzfk!a;?!CXVFjkrUiyX08hmLN{0&*){nZ zJ!v!h+WiIzE@S>o=rJ?^B27NG<16bi94M#m z-S~a>dCWjH%+>`S?*sYQzUqA|L2V<1&iT>~oDp-U@x^D48HCsirDpmn>IZTl&9Y)Q zMxyHscz!=n_sJ8o^&FOR!tr%JPXf?YaS%C6z6DcG2P);O;MLsSPK2_LZ7x^n;ej+`bL=fm`|MIgJjwIG;Z|-!u z%WSo`hjx74GnD~hseU*9mC`UQCGi1JMn6k$j?(O!i_|5IvFUo?Hm}^SzGMzDr)j|S zn=&&JSc4f;%S?CBY@SM=e+Y0R>_Jomim8qi^#g7A{)668m#1iOC{&!1WBs!83Lcx<(*abzYjJS++G=1#dF&qoMhL6{GNt1 zF{u(YaRapkoo;`Ja4!z8N+~e`;>5kxV28cWK_PE8FrF%eM|i#NnOZZ)9O(G-jntDt z=67jIx39720v%B|KfaX|Wg$vBg%h8KDrJn@YG`m8Q8Mp{ZvO=eod7OS#G)<{jPQ-- ze3#|8GuiCqPoqZ15E!SLWwqk@z95ldy|u^a9lK9%y+uY@ zWFT`MzvP~dv;g`$Xvr!PyfF}0n0Ox#kAA*B(L*5|>uSY&ToAv~^pwi~FHj^#@yWUc z`K-pIeq~z=*3;w5K=S7x?XA%3gwcLehcIbQ871|DRUbNUIBb7j^B3>}Z@eV(mILDp z*oi{Q(gri!b#}HrGy>tBprg1D>_AGtXE7eWE*FDw1Md5-BCn3F8OO%mFz#mCPs}In z`f;~mXXB9YV3|Iy0Z81@tGi@|kKcfJL44|<04*{V5C{ySH9d5rCIoD_W^V{r!SpuF z=+4CiqrzBx*#y7Bchd7uDo3L&xo0rC`DSL{xH^t4^Id$3p$Wwvn7F+oC1Hw6 z6vcV5#IEHHrn&2gb|$e_O|ep+#HmE0*G&4c<^b~2&mUFalToU&0z!w9+lQ05Mui>@lFuVMfkWODncX=TFgzW^0 zeI8bBxNn#I13Z>p9>S_0!u1ugj)=!Ne>7bVRGobiz-^P-&?qRr12Uv--doB%p1Tq= zvm%NVG+uvAE+E`#NC9Gct`GjQ!rhSAV^zPqTRF#EId#I|Ix=}uZt)_7x5HBO+rr`g zYb$zw{9t28UF#V&+EJ2WRNk8LPUCG3SlDe&CFjH}U|Nye&o4=y8;Y0f?G9GfE!3p` z0%2;9i zsoSB`WK>x1#F4)`;%g|z{a9a)>N&rr#s7BM%KXi&-2GNxn}X7xg+hnKRt3-+j2joL zf>Ul7iJ*rxaBT@($g!^+M(BzeA#r^BQy>ZK)R5 zy15VX-P?ozzPNij^NQ~xVYGc?>PcA`8!KZ3{?VGl_C_s&(5uW#bg=@U>s8V7{ z&6#{%(?@AO(IX&p0>uu%Yid~Eb4Fn7)=>xn2%7@TKPJHfjni&0r69Kg3r**;AwKc# zl)Xa#9Hs+lvMm6DLJ(83tj=iq{sI+jR!y#V5L2ueL*|qr09Ky*W0kI5l#tW)+&lNO zKlW|~ zPkZrdK8W$8O!ukntbLg)R-4K|aFE%2h_u35z6x8_!>gyBx8OebFcMCr_~8V69^=VI zbS<17qjr3rgP2?WTYUWEFOV31zr)2Nq^`Lc&M0&k>!B5n8=MRxLh5N4$4_fA&8WJa3^X)5jEd3#S^2voGpZOqdwI)>P9yY{etFfCTlBy z2$dFP>Q}JaYgpSgyFXcEyth}GDLntaloCJ0OMS~+u6IAue>5IuSh$nvJ@QK5f<_>*XH=^0n zT1@a&WljOyuv~k#op7h_y)RQ%Zl;O zWRPb53D4BbO_|1A7uQYpn5!D1lUK<4+?CU7~N2H#d1) z?4SUPp%W6W4618ocg`1NRSeU07tA}YkfaRqwDeq?!{XC-naZ4jrBr8<>|hx@cxPa< zpIYgKaRBX93ofJ|e)-OV4|+pKR7qL2(M`xvqOD(0#q66Fb(>t5CEX;9Azvb^gxbfZ zJs|4r5MFk~7mnW20&A*!=D@}zFb~(__0(&UIrdzW47q&x$rvk#Ksn(PrGYnbsX#6r zhMO<3OxH!cdlVW`nUQgGAi0D13=ndpkCqEc0 z+WZB=&O=OhNjC{hy?k;C!sB)g#y=MjsS>vQCJ7@Y99NG1h8)UptLwqJIMRv}<}jdq zNn*Y#W|zz}J6e$*tMmmt(YgS_+l>b+mPc5zIPt4=k_!W^*PzUnX;5xL7FN8kp{n>< zyBIymZv64V&;iL+*5wQnv~aqBK|dh4d|U{0ZpOb)mjypaEfQrJB>9f;btR#BgT7Iq zac>pQJ0e{K@Jdq%h0Yt>!x!``53`%POV3(0?=1r4!y!CTmSu$~(VTo${ManI#2Vh3 zT?D+R>0(>}5R*u-D0ugt4IQ`mDevH8oW?&+C9S~hj38jNp<t(}co$!2*vI(9eQPezamC`G^bfk&#WG5tV|;z>?Qi9U zP|_U?`MYjQJ;GGU&#zqzUXiu0=H-~8!YbF zF51vstwYC#meLeUxT6vWONMgsq}u(6FanT?doQs`jD~Ly zF35-@El@pHuUF{o!@HHQqjMiD-BW{ejGi%mEZn&;q6dH3E>0+8Kt*FrT&b|HCKAclS4uHoc|#nv8`1Y3KT2l*5c-@`+eqYvWJ0l8bI{5SF#0pjQX@;<0Pukp`k^#7y=486rQCbcGA zy}7>!R_Hi>t{tjxoTh-T{f#1dF)=$?3Qx=$j90Z!+JDj4{#xsy5O))Nl*e%IpTlkc zb5VLJ#nYI`i5>1A!Vk{Pt%B+T7QJ2<-d9NIglZy&B?Ob8b*9|aVtr(6&2>^ zIiFYxw0&-2-D&0Z^$dVLIG**<|JHF@;q~vPup#p8!!nzst8v=g6@?z04^)YcWK;ik{>Q!fw1qvIj9^@XN^g+63fte}3*qwpQWNzT;+w5#?NO}-6%G>Ti1EK} z75}`k!hgR1pWF)|3tiFpLkU1prm@sPeT~NMd~Q>5T4GW9T@?>-S`5tkSr#FWBHh+~ z9$PbwfkI5X39~SxRn9avd*}AXBLkFuZvQ zDk)Db?TWbiasa#+3i|#S_Gs-~XRZFYT^RSF%v&~VTg=zTSM0*=@2w!}M}uFa7$tuK z!;4fs1Dx&2TACw4{+zaSxnG(SSqSwDffB8Fqco|}r)ajtNS|~HC&9%R&;UdVfnr@I zDv_uJiNWYpK9loB0NQ9VgSe_0(ZgHRQ2cv(Yi1RQs+v2)=V04Qnj#_ovh;9C14Q~a z2$l3IEunnCblf{zm4*76nbW80)O$&BrRLtFSB@%xfnuKUu)u4TCDj%WPSqG%lXAt# z(gJ-2LzU+KPPzE!GT<7f{rA(BnmQ#_Z6|mo{Ec%r@fMD6)okMFH1SFKj=@fP={2u} zhwo14LqB)L%Ar&gJZEaQNV5cHZ0rTZ!<>`ug>c)V?DQ*eUWzK!!T$BcPKxG)6#AX-=)5rrhbh}L>jKl_(WIFA+Kr;ezb~$9JuG) z0)7Dqt5y~nJkJZ{cKBc^6eG}_z1N_a#FHc|d&Rhu0}Jm~90QNF*wkn0X0@{UyrjFu zlkAhA`;;)-W$KnF?da8J5YS{Dv+l}TX<-;kW!JUzQ8G%YO>{)8!$FV9!q{v{wx*mq z?WzYjJyZ*P)(~(UtDnp0waLr=I67pL(~)9E`iJn*h}1+kn@`Nb9s5dn)E}?y%(2A zUlthp;v#-k8tFdlvhfxz6or>rGz-Fa9ach>l4I~8FM|c9)mIwUmhF8qU9OHMTf|Y69S=ZzcE!+M399B-I_Qlt-*TrGGXa!wMdF1CNNVl;(upT5A)R zkf~@*Z*FRR$MEJZE35x|Z*a8CHCR;#0&Yi!<^eo&rv$KnFJC9 z_cbBqDt1NYW3W{Ymg=`W6!3E@;497gAnZg?(oW75GhT$w3fm*DYg0qt?un!xQHI@$ zIfl?7(+2uhcv5s9VFs~0u|ceAW9A;FB4n zLeUcG(0IluHuT0a;!(<@sf!PcZfoe@99%M%wC}$)$*X9n$7s2;dLdY7k)WyHk-Xn? z`ud^kgqQ9%?OF&8y=*m)P42x@n#Up$`s6P|!Ow8;1p~-6EBe)egU=tlmK5+`Q;7*7 z3ZRn(Q96sUk1(foZRuqVSeGwt^^+Ode#7)72o80PV6y>GMr4HYkSJy0l;^=uNTW?Q z2Va{(mxwo}{AD#Cb%I+T*&5($!^2cTr-)IK@RXbKEuv~v_k#529G=PCpy^LRvx1Q! zYRme*LrMw({TUw?98c{Uv!BrRIJZmALDFmV9MSKkY9f|D%-&dIms#Emq zTAJD8c5)pS4|W&dZzk^39x$;HAE$2wG81$((ddav*Ud*tGsRvtVHD)-V;*E!=-=)` ztDb$9p^bft@0p*!mAOl?W*|QNdiqf_59)xgf^WOajzs&y1esE%tvXyCEWN0x^B_}F*nlhSKh3>Rs zaKVrTl*%NOd3!qel(Nx<-r_Xy8|=J2n)lt@yq4(O)Tr<=WFB{&yZ?O|8_|2=B`$_>2HYk=(e$CkwvLo;pjmin>l;~MMUdb!de9UOgz9c#bY*8DSq;O{S1CE zxLB1`Pv(Ky$0ib!b*;_`@FNdI0V4bFsPJU%3V?6|lM?QM<_G}N2p#U}__dijgAn>B zoi6$@kGx%*po80fKnR!km>KbPy1LdlCMef!q$Ak#O-PIAk7vJy)^GH>D6Sv1)a|hO zA_RUtr8y4jkQn*>MT>;XV|oBkg|4{mb82rZB497*oh>H>s7LkWKx)Q!za|?$-(VIT zivJ9*l1BmB;>aZ6O0tH3T-k@H69lWfkB3T=9awc+MJf39a(dfN-H@n^NbY^~^DNlq z?C^c~`M5=Hj55P2K}fv`m|wo3q-s~ACS7yu`P10pi^B=wLX;l2jsWLF-50 z1UbL}cLBi3>hX*M7-Yjt0pje=CpD^`moKk@_;Cuhd_i3(@rkt=AZowEAYjBO;0EK| z&FgOLc29jkZFCol(ts&dsut-#&+^QT30%j=2iNXSZ7;J__kT-uZ~dn2HY~I4@0z~> z4<(6C@i$G3Y^C~isnv+2l&93Bc3!zj?{PsXI5Jd;C1ifI+l_iLND~obEuT~z9~so& zb^5erKPs4M3d8;Zh_hPg*>RUX+b#0wEO4Q4op`XK`pwYxF^rUqNxF54kw&nw^AZLo5||W5uXTT$b0GONx%7J z_|t2x{-wV_wXLyy^T*O;`)P7X&$xmbep0@36)@4y-K(Yyj%;^n!v&Qjz#Vx*G~1eT zRv?)cIINH1qXgAos3Pg%%M(XgO3G5EipDHdLQRIW@Khx0UKX}e5vv(3vrk9xM$z56 zle|rYGVw7XaqW9SdOsfv`i$b{Bk^_wcoOMge#Y1ElaB|_mc3EQxQdwgDrI(G z;U*v_D(|U*Y{!763t}R7-0J2>;Z_oW%GW*=2JZE3LHF&yKH+1o?ns{OIdy;XV*Ds5 ziheVMSRK zbCfIn*;;o1rThe*G4R!r@QL?cH5H&90%G5o)`jA~;?d!00W)BOx)6bHFXOj45d+T? zr?|QZi`aP0yXb1%O1R)a`A$@ZfV@{+cHZ@$CQYNyxwhaAA7m{>@J8=SPM(#!ld!dk z=d>xktN2^zWztzr!L|*Kvf+!*?H`_If@W*a-yiu&yPubQe8x~Q*jki%%}N|#!)p;@ zx0c@MHK^lCdNW6(U~U1p?OufViziEB#U9#J8b8VX7KRSV7CH7Uu~y1~cmD#tcG!ii z$CkDmHObnIR(&@GTJeM!&?)18aia1FZ&DIwlYw2OXq zUC;MuIlu581Ra^EVE1xGJ!Ov0=Qc66*+V>*rGjX!3x{z1ZL7!bH7y!FRR{XpNhZ^Q3)iB9DXnVOfw!$x}0XM zTkX{2K44sT`iOr5W`TDeSTI6=bo6k{yq5XtQyn?yTiKrsoq!6OvlGN**$&9qJBP8O zv@&rIlB_yO-bIO%NDh&pX!+B%<2=R@6AOy#R9b_A`K%j|`oeA)JimYLFmp#k&mv#h zZL0F|7zs*P?c%aE1-c~mkdUhd(fLh@9(xZvslsa86$z-!CisRAZ5wTp zM{Tr&;jBgFk6XgCUz(*fVLdNODfoaWCIU^9HE}iu+I3r1acbLVD^@bGI50^&5c2B; z7~wcWAT0!pMxCBFd}Psc7mq9BdB|_?Bk}^olVZNz#VZRO=?R_;RmFCm4lg9^Y*+yT zCR7U`U>qspC)=$SdLDwqp2`E;lC8`>>xR{3nD~Yg_rMI`x)!%TsqHV9u#Tr|H1ydl z-K-G$QeD^Yygk2bcq*8~ z-(Q+7_Jsq|K0x0{E#96Md-c4yvnfWPE3hlYG5oiIvKH$m;3tUK>XslZ?w11)JKzb}D>o*Y&u zVef(P!N-AW*jyf>=45Wq0#o?D*EiRwbWiu|HgES@Kb4m%ohHkpj0r(&-vXJ|MT7XE zCJsYsTZSA=qBhU~5Qq=R;Yk0c0$C_TK)qfA(&J!*whfG#`q(eW4ksmqVTw+h?(Gfh zbQjkRHe~}w2p~#Qn5`cJhZAr91xg9U1BM|K>RkBVNetdC%9O9t2H(@q6|s^Y;AZE3 zoO^A{%vWpk^Nz-hAoz+y!t{LLp$B}|G*-?{VJ7{E^Z_RB4xB9Ag;LKs(h z+Uiwm)Q9*Vkcl9m)Lk5~LlwROtk#ugoNOEG81;RuUA@u_~$_oNsal%})@=@ZiX`wE^=lo2KuV!`Okmu}Ym9n60c(WUC|iq1Ag1=-eKcTAzWUPtV&SlB z*JFROgkSl1rj3M#4J*o3Cqxc)$HZDjp}4Pff}w0n^`NBiW}NrFl@@{qpQzV1t<3Q( zEiGRsbFBW=YS()A>%rddf%F8L z#gmrA?h4Ylq*7YjdXV{b;jAfajfN2geu!8xp?uNU(kYkD%USVEdsm|H@T3O_=rAAU z=X^9cc!eb6u%Q>0GGNJ^rHjzo7kna9gc`Ty>itss9YzU-lsG-rkvXW`zBBxmM`Vxo z)0!70UsxsbZ>I{rqgNU~8z*MkzcN>0GCx>soO%{|eN!YiOQ;O!o=z?ZTr}&WeIGq0 zOr#d=42CV6AY*&|H6y(OE(L6G%7oe4?)LnAEYG9d!$cJbNW=1Yk=lr|~vk9=UcsxJqm z$#Tz>c1-1QyL<7h9rF!M*Q6{(&wr=O8~y4&pHSoriD}EkJla5n%j7ZFP6` zN2P-rCsw4s4y`p#Z$|RSDz_K;o9!&5m93e@>*-AHxpy?0@->sOUMJTB+v#2Zx<5!QaD zvNi-tmUhb%UM=Jr#Dk+r{KQ9f3T&zCI`M+K*-^PN{cD`6U+Ck?Wl#83g&;3MyX}ojW6LL1mKt9FcZU;m~bc8XT2hwme46m7X0`w%hJz@7X?!r%I;LMgk z*4${i)=)GHj@3h37(c%c{jeeKH2I^SN}*MP8>>CL2;+`wyaQlH*_11B`TR`9$?(Bf z(Hy$#qu^5cpJbq4nZI|gn!y)_3Tr^no^0@df$rd(El)BEM zUfKxx(HhF|pU$6Pf9Fhc2P#fX_GIpdySeq{9qg9G)y9Vx0JYiaMd`((Pq|v3J(vt< zbV_149D+_f=M8y@IE0c6_Y}NQbM?bIcRZ{k1V_)x!AB@&Tt`Hp9-eN4M_C#loU;%V zkzv$OmwGR4!6WAKVg041l&PaN{N54h-y;)8_YOM};0M|=At!4M z!X&qR+9YxO)6DGs7K9iLsgolHBGb=@-L}PpXe>F;VBZwJ#KNN=R1~ew(HN}`%NdQb z5jk(gcRFzXotK>roAGd()5C#FJ}ne27P|^Kr}Bl0a@8U)GDd zfqdx1^})0IqX0^=Dt0fo7s2$O|M7rqkU)8Xh(LrP2Z$bYJw=tZSDYRg2n`(TO09$Q zCX488UgQRVA*Q%tcr@fP5gNAGo5PfAl%T9UNqez30Q2MxNo5Qiu)*{yhaOK=AH5*c z_Vy>^V(it6wYlauJ^_g_pWDfnSHAPz#xuS8`fkXDR-2z<@AcCp8!_DfQr0zoG1v&tvu3JN!U(|7bp6t65YDs>@ge@L6 zn=top!W7g0KmjTUliHI_j>s&#g{(# z=M&{=>gqgkRKc0XDnFcT7m;SDHJ^@26;L6@}ErrhidRWvP2 zSik4e6KQkEan;qyk26nw537pxTC6}+=EoqL)s&S`MTLY7es>o!u@}%gQFsm@=c?l) z6004nrA!xds*&{J3^D&){R?OweOqI3y6qMLQD|I`Dp^$OzdOQ`AxFQKe70*JTs0bBiyOEZMmSV*@0)5Ck{42|v1y z#e>{DOsalj4L;Ka;U0S!CXOVHol_akk_3^91OAWO`Njg-gKVyD{6<`Aw(sWS^kp?v z@n_LfQ(-vIE(jP2=;LZOU_KTWYWA@g#B~zC=K#IaYq?#yf>qsvKA)LQ$?u00OoyyH z9Sp?FIaIhf1serzKS9o_uMgt+2Pgo|)Tu+;lR#^NpC?x7+QOMu=Zx3JeKns|8K$eA zb75qZXQx}CN)qjX_)+464ktIp`JO|WEYm28_s{L$0aC*cC{v;h{zGm_RaPm1@ifR@ z=AgF~YM9hQEqrksC$CgvnoA3#s#M?L-sdJX&_0{Z!n}j*Yqsu7LV5;1u~(0P0vP_o zF#!J!A^`q>dEI`ifOiX;*>Q#o`L>C=TYj<-lvd_=oEcHvwp@n|1Tv_Zt39NOF8YDS zz;gcv+%Bzjars$k(@-BK8h}iH?tb?rGdPbTROD~<2_WA1^Y!N*{HcLIHSnhf{?x#q n8u(KKe`?@Q4g9HrKQ-{D2L9B*pBng61Al7ZzeElEHUB>V@vB9w literal 0 HcmV?d00001 diff --git a/scripts/text-detection.py b/scripts/text-detection.py index 47effc0b6..e8460a402 100644 --- a/scripts/text-detection.py +++ b/scripts/text-detection.py @@ -74,9 +74,10 @@ def main(): print("Usage: python text-detection.py ") sys.exit(1) - cap = cv.VideoCapture(sys.argv[1]) + input_video_file = sys.argv[1] + cap = cv.VideoCapture(input_video_file) if not cap.isOpened(): - print("Error: Could not open video file.") + print("Fatal: Could not open video file.") sys.exit(1) frame_idx = 0 @@ -95,13 +96,57 @@ def main(): for future in futures: time_differences.append(future.result()) - cap.release() + # Filter out zero values from time_differences + non_zero_time_differences = [td for td in time_differences if td != 0] + + # Calculate the average latency excluding zero values + if non_zero_time_differences: + average_latency = np.mean(non_zero_time_differences) - plt.plot(time_differences, marker='o') - plt.title('Media Communications Mesh Latency') + # Filter out anomaly peaks that differ more than 25% from the average for average calculation + filtered_time_differences = [ + td for td in non_zero_time_differences if abs(td - average_latency) <= 0.25 * average_latency + ] + + # Calculate the average latency using the filtered data + filtered_average_latency = np.mean(filtered_time_differences) + else: + print("Fatal: No timestamps recognized in the video. No data for calculating latency.") + sys.exit(1) + + # Plot the non-zero data + plt.plot(non_zero_time_differences, marker='o') + plt.title('End-to-End Latency — Media Communications Mesh') plt.xlabel('Frame Index') - plt.ylabel('Measured latency (ms)') + plt.ylabel('Latency, ms') plt.grid(True) + + # Adjust the layout to create more space for the text + plt.subplots_adjust(bottom=0.5) + + # Prepare text for display and stdout + average_latency_text = f'Average End-to-End Latency: {filtered_average_latency:.2f} ms' + file_name = os.path.basename(input_video_file) + file_mod_time = datetime.fromtimestamp(os.path.getmtime(input_video_file)).strftime('%Y-%m-%d %H:%M:%S') + file_info_text = f'File: {file_name} | Last modified: {file_mod_time} UTC' + width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)) + fps = cap.get(cv.CAP_PROP_FPS) + video_properties_text = f'Resolution: {width}x{height} | FPS: {fps:.2f}' + + cap.release() + + # Display text on the plot + plt.text(0.5, -0.55, average_latency_text, + horizontalalignment='center', verticalalignment='center', + transform=plt.gca().transAxes) + plt.text(0.5, -0.85, file_info_text, + horizontalalignment='center', verticalalignment='center', + transform=plt.gca().transAxes) + plt.text(0.5, -1, video_properties_text, + horizontalalignment='center', verticalalignment='center', + transform=plt.gca().transAxes) + if is_display_attached(): plt.show() @@ -109,7 +154,12 @@ def main(): filename = sys.argv[2] if not filename.endswith('.jpg'): filename += '.jpg' - print("Saving the plot to: ", filename) + print("Saving the latency chart to: ", filename) plt.savefig(filename, format='jpg', dpi=300) - + + # Print text to stdout + print(file_info_text) + print(video_properties_text) + print(average_latency_text) + main() From c54db13e16e022a783d33b5aaac04126c17601c1 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Tue, 3 Jun 2025 08:19:26 +0000 Subject: [PATCH 7/8] Remove unnecessary change in docs/FFmpegPlugin.md --- docs/FFmpegPlugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FFmpegPlugin.md b/docs/FFmpegPlugin.md index b687cd456..8b33d53fa 100644 --- a/docs/FFmpegPlugin.md +++ b/docs/FFmpegPlugin.md @@ -23,7 +23,7 @@ replace `7.0` with `6.1` in the following script. 1. Run the FFmpeg configuration tool ```bash - ./configure-ffmpeg.sh + ./configure-ffmpeg.sh ``` 1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin From 63682ba65dda5856974da902f30e3a8e1c42cb20 Mon Sep 17 00:00:00 2001 From: Tomasz Szumski Date: Tue, 3 Jun 2025 10:00:33 +0000 Subject: [PATCH 8/8] Adjust instruction --- docs/LatencyMeasurement.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/LatencyMeasurement.md b/docs/LatencyMeasurement.md index 4a10c1b23..fbbace298 100644 --- a/docs/LatencyMeasurement.md +++ b/docs/LatencyMeasurement.md @@ -288,6 +288,9 @@ This example demonstrates sending a video file from the 1st FFmpeg instance to t See the [Sample latency diagram](#sample-latency-diagram). +## Customization +When modifying FFmpeg commands if you change parameters of `drawtext` filter, especialy `fontsize`, `x`, `y` or `text`, you have to adjust python script __text-detection.py__ too, please refer to function `extract_text_from_region(image, x, y, font_size, length)` + [license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg