|
| 1 | +# End-to-End Latency Measurement — Media Communications Mesh |
| 2 | + |
| 3 | +This document describes a simple solution for measuring end-to-end latency in Media Communications Mesh. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +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). |
| 8 | + |
| 9 | +> Only video payload is supported. |
| 10 | +
|
| 11 | +```mermaid |
| 12 | +flowchart LR |
| 13 | + tx-file((Input |
| 14 | + video file)) |
| 15 | + tx-ffmpeg(Tx |
| 16 | + FFmpeg) |
| 17 | + subgraph mesh [Media Communications Mesh] |
| 18 | + direction LR |
| 19 | + proxy1(Proxy1) |
| 20 | + proxy2a(. . .) |
| 21 | + proxy2b(. . .) |
| 22 | + proxy2c(. . .) |
| 23 | + proxy3(ProxyN) |
| 24 | + proxy1 --> proxy2a --> proxy3 |
| 25 | + proxy1 --> proxy2b --> proxy3 |
| 26 | + proxy1 --> proxy2c --> proxy3 |
| 27 | + end |
| 28 | + rx-ffmpeg(Rx |
| 29 | + FFmpeg) |
| 30 | + rx-file((Output |
| 31 | + video file)) |
| 32 | +
|
| 33 | + tx-file --> tx-ffmpeg --> mesh --> rx-ffmpeg --> rx-file |
| 34 | +``` |
| 35 | + |
| 36 | +## How it works |
| 37 | + |
| 38 | +1. Tx side – The user starts FFmpeg with special configuration to stream video via the Mesh. |
| 39 | +1. Rx side – The user starts FFmpeg with special configuration to receive the video stream from the Mesh. |
| 40 | +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. |
| 41 | +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. |
| 42 | +1. After transmission is done, there is a resulting MPEG video file on the disk on the Rx side. |
| 43 | +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. |
| 44 | + |
| 45 | +## Sample latency diagram |
| 46 | + |
| 47 | +<img src="_static/ffmpeg-based-latency-solution-diagram.jpg" width="520"> |
| 48 | + |
| 49 | +## Important notice on latency measurement results |
| 50 | + |
| 51 | +> 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. |
| 52 | +
|
| 53 | + |
| 54 | +## Build and install steps |
| 55 | + |
| 56 | +> It is assumed that Media Communications Mesh is installed on the Tx and Rx host machines according to [Setup Guide](SetupGuid.md). |
| 57 | +
|
| 58 | +If [FFmpeg Plugin](FFmpegPlugin.md) was installed earlier, remove its directory before proceeding with the following. |
| 59 | + |
| 60 | +1. Clone the FFmpeg 7.0 repository and apply patches. |
| 61 | + |
| 62 | + ```bash |
| 63 | + ./clone-and-patch-ffmpeg.sh |
| 64 | + ``` |
| 65 | + |
| 66 | +1. Run the FFmpeg configuration tool with special features enabled |
| 67 | + |
| 68 | + ```bash |
| 69 | + ./configure-ffmpeg.sh 7.0 --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig |
| 70 | + ``` |
| 71 | + |
| 72 | +1. Build and install FFmpeg with the Media Communications Mesh FFmpeg plugin |
| 73 | + |
| 74 | + ```bash |
| 75 | + ./build-ffmpeg.sh |
| 76 | + ``` |
| 77 | + |
| 78 | +1. Install Tesseract OCR |
| 79 | + ```bash |
| 80 | + apt install tesseract-ocr |
| 81 | + ``` |
| 82 | +1. Install Python packages |
| 83 | + ```bash |
| 84 | + pip install opencv-python~=4.11.0 pytesseract~=0.3.13 matplotlib~=3.10.3 |
| 85 | + ``` |
| 86 | + |
| 87 | +1. Setup time synchronization on host machines |
| 88 | + |
| 89 | + > Make sure `network_interface_1` and `network_interface_2` are connected to the same network. |
| 90 | +
|
| 91 | + * __host-1 Controller clock__ |
| 92 | + ```bash |
| 93 | + sudo ptp4l -i <network_interface_1> -m 2 |
| 94 | + sudo phc2sys -a -r -r -m |
| 95 | + ``` |
| 96 | + |
| 97 | + * __host-2 Worker clock__ |
| 98 | + ```bash |
| 99 | + sudo ptp4l -i <network_interface_2> -m 2 -s |
| 100 | + sudo phc2sys -a -r |
| 101 | + ``` |
| 102 | + |
| 103 | +## Example – Measuring transmission latency between two FFmpeg instances on the same host |
| 104 | + |
| 105 | +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. |
| 106 | + |
| 107 | + |
| 108 | +1. Run Mesh Agent |
| 109 | + ```bash |
| 110 | + mesh-agent |
| 111 | + ``` |
| 112 | + |
| 113 | +1. Run Media Proxy |
| 114 | + |
| 115 | + ```bash |
| 116 | + sudo media_proxy \ |
| 117 | + -d 0000:32:01.1 \ |
| 118 | + -i 192.168.96.11 \ |
| 119 | + -r 192.168.97.11 \ |
| 120 | + -p 9200-9299 \ |
| 121 | + -t 8002 |
| 122 | + ``` |
| 123 | + |
| 124 | +1. Start the Receiver side FFmpeg instance |
| 125 | + |
| 126 | + ```bash |
| 127 | + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ |
| 128 | + -f mcm \ |
| 129 | + -conn_type multipoint-group \ |
| 130 | + -frame_rate 60 \ |
| 131 | + -video_size 1920x1080 \ |
| 132 | + -pixel_format yuv422p10le \ |
| 133 | + -i - \ |
| 134 | + -vf \ |
| 135 | + "drawtext=fontsize=40: \ |
| 136 | + text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ |
| 137 | + x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ |
| 138 | + -vcodec mpeg4 -qscale:v 3 recv.mp4 |
| 139 | + ``` |
| 140 | +1. Start the Sender side FFmpeg instance |
| 141 | + |
| 142 | + ```bash |
| 143 | + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg -i <video-file-path> \ |
| 144 | + -vf \ |
| 145 | + "drawtext=fontsize=40: \ |
| 146 | + text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ |
| 147 | + x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ |
| 148 | + -f mcm \ |
| 149 | + -conn_type multipoint-group \ |
| 150 | + -frame_rate 60 \ |
| 151 | + -video_size 1920x1080 \ |
| 152 | + -pixel_format yuv422p10le - |
| 153 | + ``` |
| 154 | + |
| 155 | + 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`: |
| 156 | + |
| 157 | + ```bash |
| 158 | + ffmpeg -f rawvideo -pix_fmt yuv422p10le -s 1920x1080 -i <video-file-path> ... |
| 159 | + ``` |
| 160 | + |
| 161 | + It is also recommended to provide the read rate `-readrate` at which FFmpeg will read frames from the file: |
| 162 | + |
| 163 | + ```bash |
| 164 | + ffmpeg -f rawvideo -readrate 2.4 -pix_fmt yuv422p10le -s 1920x1080 -i <video-file-path> ... |
| 165 | + ``` |
| 166 | + |
| 167 | + 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. |
| 168 | + |
| 169 | + | frame_rate | readrate | |
| 170 | + |------------|-------------------| |
| 171 | + | 25 | 25 / 25 = 1 | |
| 172 | + | 50 | 50 / 25 = 2 | |
| 173 | + | 60 | 60 / 25 = 2.4 | |
| 174 | + |
| 175 | +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. |
| 176 | + |
| 177 | + ```bash |
| 178 | + python text-detection.py recv.mp4 recv-latency.jpg |
| 179 | + ``` |
| 180 | + |
| 181 | + Console output |
| 182 | + ```bash |
| 183 | + ... |
| 184 | + Processing Frame: 235 |
| 185 | + Processing Frame: 236 |
| 186 | + Processing Frame: 237 |
| 187 | + Processing Frame: 238 |
| 188 | + Processing Frame: 239 |
| 189 | + Processing Frame: 240 |
| 190 | + Saving the latency chart to: recv-latency.jpg |
| 191 | + File: recv.mp4 | Last modified: 2025-06-02 13:49:54 UTC |
| 192 | + Resolution: 640x360 | FPS: 25.00 |
| 193 | + Average End-to-End Latency: 564.61 ms |
| 194 | + ``` |
| 195 | + |
| 196 | + See the [Sample latency diagram](#sample-latency-diagram). |
| 197 | + |
| 198 | + |
| 199 | +## Example – Measuring transmission latency between two FFmpeg instances on different hosts |
| 200 | + |
| 201 | +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. |
| 202 | + |
| 203 | +1. Run Mesh Agent |
| 204 | + ```bash |
| 205 | + mesh-agent |
| 206 | + ``` |
| 207 | + |
| 208 | +1. Start Media Proxy on the Receiver host machine |
| 209 | + |
| 210 | + ```bash |
| 211 | + sudo media_proxy \ |
| 212 | + -d 0000:32:01.1 \ |
| 213 | + -i 192.168.96.11 \ |
| 214 | + -r 192.168.97.11 \ |
| 215 | + -p 9200-9299 \ |
| 216 | + -t 8002 |
| 217 | + ``` |
| 218 | + |
| 219 | +1. Start the Receiver side FFmpeg instance |
| 220 | + |
| 221 | + ```bash |
| 222 | + sudo MCM_MEDIA_PROXY_PORT=8002 ffmpeg \ |
| 223 | + -f mcm \ |
| 224 | + -conn_type st2110 \ |
| 225 | + -transport st2110-20 \ |
| 226 | + -ip_addr 192.168.96.10 \ |
| 227 | + -port 9001 \ |
| 228 | + -frame_rate 60 \ |
| 229 | + -video_size 1920x1080 \ |
| 230 | + -pixel_format yuv422p10le \ |
| 231 | + -i - \ |
| 232 | + -vf \ |
| 233 | + "drawtext=fontsize=40: \ |
| 234 | + text='Rx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ |
| 235 | + x=10: y=70: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ |
| 236 | + -vcodec mpeg4 -qscale:v 3 recv.mp4 |
| 237 | + ``` |
| 238 | + |
| 239 | +1. Start Media Proxy on the Sender host machine |
| 240 | + |
| 241 | + ```bash |
| 242 | + sudo media_proxy \ |
| 243 | + -d 0000:32:01.0 \ |
| 244 | + -i 192.168.96.10 \ |
| 245 | + -r 192.168.97.10 \ |
| 246 | + -p 9100-9199 \ |
| 247 | + -t 8001 |
| 248 | + ``` |
| 249 | + |
| 250 | +1. Start the Sender side FFmpeg instance |
| 251 | + |
| 252 | + ```bash |
| 253 | + sudo MCM_MEDIA_PROXY_PORT=8001 ffmpeg -i <video-file-path> \ |
| 254 | + -vf \ |
| 255 | + "drawtext=fontsize=40: \ |
| 256 | + text='Tx timestamp %{localtime\\:%H\\\\\:%M\\\\\:%S\\\\\:%3N}': \ |
| 257 | + x=10: y=10: fontcolor=white: box=1: boxcolor=black: boxborderw=10" \ |
| 258 | + -f mcm \ |
| 259 | + -conn_type st2110 \ |
| 260 | + -transport st2110-20 \ |
| 261 | + -ip_addr 192.168.96.11 \ |
| 262 | + -port 9001 \ |
| 263 | + -frame_rate 60 \ |
| 264 | + -video_size 1920x1080 \ |
| 265 | + -pixel_format yuv422p10le - |
| 266 | + ``` |
| 267 | + |
| 268 | +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. |
| 269 | + |
| 270 | + ```bash |
| 271 | + python text-detection.py recv.mp4 recv-latency.jpg |
| 272 | + ``` |
| 273 | + |
| 274 | + Console output |
| 275 | + ```bash |
| 276 | + ... |
| 277 | + Processing Frame: 235 |
| 278 | + Processing Frame: 236 |
| 279 | + Processing Frame: 237 |
| 280 | + Processing Frame: 238 |
| 281 | + Processing Frame: 239 |
| 282 | + Processing Frame: 240 |
| 283 | + Saving the latency chart to: recv-latency.jpg |
| 284 | + File: recv.mp4 | Last modified: 2025-06-02 13:49:54 UTC |
| 285 | + Resolution: 640x360 | FPS: 25.00 |
| 286 | + Average End-to-End Latency: 564.61 ms |
| 287 | + ``` |
| 288 | + |
| 289 | + See the [Sample latency diagram](#sample-latency-diagram). |
| 290 | + |
| 291 | +## Customization |
| 292 | +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)` |
| 293 | +
|
| 294 | +
|
| 295 | +<!-- References --> |
| 296 | +[license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg |
| 297 | +[license]: https://opensource.org/license/bsd-3-clause |
0 commit comments