Skip to content

Commit 318a9fd

Browse files
committed
docs: Add stream postprocessing instructions and Python script for latency analysis
1 parent 7d92d1f commit 318a9fd

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

docs/LatencyMeasurement.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ Before reading this document, please read [FFmpeg Plugin](FFmpegPlugin.md) to fa
1010
```bash
1111
./configure-ffmpeg.sh --enable-libfreetype --enable-libharfbuzz --enable-libfontconfig
1212
```
13+
## Time synchronization between hosts
14+
15+
__host-1 Controller clock__
16+
```bash
17+
sudo ptp4l -i <network_interface_1> -m 2
18+
```
19+
__host-2 Worker clock__
20+
```bash
21+
sudo ptp4l -i <network_interface_2> -m 2 -s
22+
sudo -s phc2sys -s <network_interface_2> -c CLOCK_REALTIME -O 0 -m
23+
```
24+
*Please note that `network_interface_1` and `network_interface_2` have to be physically connected to the same network
25+
26+
1327
## Example – Run video transmission between 2 FFmpeg instances on the same host
1428

1529
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
154168
-pixel_format yuv422p10le -
155169
```
156170

171+
## Stream postprocessing
172+
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.
173+
174+
1. install reuired python packages
175+
```bash
176+
apt install tesseract-ocr
177+
pip install opencv-python
178+
pip install pytesseract
179+
pip install matplotlib
180+
```
181+
2. Postprocess stream with command
182+
```bash
183+
python text-detection.py <input_video_file> <output_image_name>
184+
```
185+
```bash
186+
python text-detection.py recv.mp4 latency_chart.jpg
187+
```
188+
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)`
189+
190+
157191
<!-- References -->
158192
[license-img]: https://img.shields.io/badge/License-BSD_3--Clause-blue.svg
159193
[license]: https://opensource.org/license/bsd-3-clause

scripts/text-detection.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import sys
2+
import pytesseract
3+
import cv2 as cv
4+
import numpy as np
5+
from datetime import datetime
6+
import re
7+
import matplotlib.pyplot as plt
8+
from concurrent.futures import ThreadPoolExecutor
9+
import os
10+
11+
def is_display_attached():
12+
# Check if the DISPLAY environment variable is set
13+
return 'DISPLAY' in os.environ
14+
15+
def extract_text_from_region(image, x, y, font_size, length):
16+
"""
17+
Extracts text from a specific region of the image.
18+
:param image: The image to extract text from.
19+
:param x: The x-coordinate of the top-left corner of the region.
20+
:param y: The y-coordinate of the top-left corner of the region.
21+
:param font_size: The font size of the text.
22+
:param length: The length of the text to extract.
23+
:return: The extracted text.
24+
"""
25+
margin = 5
26+
y_adjusted = max(0, y - margin)
27+
x_adjusted = max(0, x - margin)
28+
height = y + font_size + margin
29+
width = x + length + margin
30+
# Define the region of interest (ROI) for text extraction
31+
roi = image[y_adjusted:height, x_adjusted:width]
32+
33+
# Use Tesseract to extract text from the ROI
34+
return pytesseract.image_to_string(roi, lang='eng')
35+
36+
def process_frame(frame_idx, frame):
37+
print("Processing Frame: ", frame_idx)
38+
39+
timestamp_format = "%H:%M:%S:%f"
40+
timestamp_pattern = r'\b\d{2}:\d{2}:\d{2}:\d{3}\b'
41+
42+
# Convert frame to grayscale for better OCR performance
43+
frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
44+
45+
line_1 = extract_text_from_region(frame, 10, 10, 50, 700)
46+
line_2 = extract_text_from_region(frame, 10, 70, 50, 700)
47+
48+
# Find the timestamps(Type: string) in the extracted text using regex
49+
tx_time = re.search(timestamp_pattern, line_1)
50+
rx_time = re.search(timestamp_pattern, line_2)
51+
52+
if tx_time is None or rx_time is None:
53+
print("Error: Timestamp not found in the expected format.")
54+
return 0
55+
56+
# Convert the timestamps(Type: string) to time (Type: datetime)
57+
tx_time = datetime.strptime(tx_time.group(), timestamp_format)
58+
rx_time = datetime.strptime(rx_time.group(), timestamp_format)
59+
60+
if tx_time is None or rx_time is None:
61+
print("Error: Timestamp not found in the expected format.")
62+
return 0
63+
64+
if tx_time > rx_time:
65+
print("Error: Transmit time is greater than receive time.")
66+
return 0
67+
68+
time_difference = rx_time - tx_time
69+
time_difference_ms = time_difference.total_seconds() * 1000
70+
return time_difference_ms
71+
72+
def main():
73+
if len(sys.argv) < 2:
74+
print("Usage: python text-detection.py <input_video_file> <output_image_name>")
75+
sys.exit(1)
76+
77+
cap = cv.VideoCapture(sys.argv[1])
78+
if not cap.isOpened():
79+
print("Error: Could not open video file.")
80+
sys.exit(1)
81+
82+
frame_idx = 0
83+
time_differences = []
84+
85+
with ThreadPoolExecutor(max_workers=40) as executor:
86+
futures = []
87+
while True:
88+
ret, frame = cap.read()
89+
if not ret:
90+
break
91+
92+
futures.append(executor.submit(process_frame, frame_idx, frame))
93+
frame_idx += 1
94+
95+
for future in futures:
96+
time_differences.append(future.result())
97+
98+
cap.release()
99+
100+
plt.plot(time_differences, marker='o')
101+
plt.title('Media Communication Mesh Latency')
102+
plt.xlabel('Frame Index')
103+
plt.ylabel('Measured latency (ms)')
104+
plt.grid(True)
105+
if is_display_attached():
106+
plt.show()
107+
108+
if len(sys.argv) == 3:
109+
filename = sys.argv[2]
110+
if not filename.endswith('.jpg'):
111+
filename += '.jpg'
112+
print("Saving the plot to: ", filename)
113+
plt.savefig(filename, format='jpg', dpi=300)
114+
115+
main()

0 commit comments

Comments
 (0)