In this tutorial, we will explore how to send and receive video frames over a network using Python sockets. We will break down both the server and client code to understand how video data is transmitted and displayed in real-time.
Overview
- Server Code: The server listens for incoming connections, receives video frames from the client, and displays them.
- Client Code: The client reads video frames from a video file, sends them to the server, and displays them locally.
Server Code Breakdown
import socket
import cv2
import pickle
import struct
def recv_exact(sock, size):
"""Receive exactly `size` bytes from the socket."""
data = b""
while len(data) < size:
packet = sock.recv(size - len(data))
if not packet:
return None
data += packet
return data
# Create a TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 12345))
server_socket.listen(1)
print("Waiting for client connection...")
client_socket, addr = server_socket.accept()
print("Connected to:", addr)
try:
while True:
# Receive the frame size (4 bytes)
packed_size = client_socket.recv(4)
if not packed_size:
break
frame_size = struct.unpack("I", packed_size)[0]
# Receive the complete frame data
frame_data = recv_exact(client_socket, frame_size)
if frame_data is None:
break
# Deserialize and decode the frame
frame = cv2.imdecode(pickle.loads(frame_data), cv2.IMREAD_COLOR)
# Display the frame
cv2.imshow("Server Camera", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except Exception as e:
print("Error:", e)
finally:
cv2.destroyAllWindows()
client_socket.close()
server_socket.close()
print("Connection closed.")
Key Functions and Concepts in Server Code
- recv_exact(sock, size):
- This function ensures that exactly size bytes are received from the socket. It is necessary because recv() may not always return the exact number of bytes requested.
Parameters:
- sock: The socket object.
- size: The number of bytes to receive.
2. socket.socket(socket.AF_INET, socket.SOCK_STREAM):
- Creates a TCP socket.
- AF_INET specifies the address family (IPv4).
- SOCK_STREAM specifies the socket type (TCP).
3. server_socket.bind(('localhost', 12345)):
- Binds the socket to the localhost on port 12345.
4. server_socket.listen(1):
- Listens for incoming connections. The argument 1 specifies the maximum number of pending connections.
5. client_socket, addr = server_socket.accept():
- Accepts a connection from a client. Returns a new socket object (client_socket) and the address of the client (addr).
6. struct.unpack("I", packed_size)[0]:
- Unpacks the frame size from the received 4-byte data. "I" specifies an unsigned integer.
7. cv2.imdecode(pickle.loads(frame_data), cv2.IMREAD_COLOR):
- Deserializes the frame data using pickle.loads() and decodes it into an image using cv2.imdecode().
8. cv2.imshow("Server Camera", frame):
- Displays the received frame in a window titled “Server Camera”.
9. cv2.waitKey(1):
- Waits for 1 millisecond for a key event. If the ‘q’ key is pressed, the loop breaks.
Client Code Breakdown
import socket
import cv2
import pickle
import struct
import time
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('localhost', 12345))
video_path = 'mix.mp4' # Replace with your video file path
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
print(f"Video FPS: {fps}")
try:
while True:
start_time = time.time()
ret, frame = cap.read()
if not ret:
break
# Resize and compress the frame
frame = cv2.resize(frame, (640, 480))
ret, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
data = pickle.dumps(buffer)
message_size = struct.pack("I", len(data))
client_socket.sendall(message_size + data)
cv2.imshow("Client Camera", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
time_to_sleep = max(0, (1 / fps) - (time.time() - start_time))
time.sleep(time_to_sleep)
except KeyboardInterrupt:
print("Client stopped.")
finally:
cap.release()
client_socket.close()
cv2.destroyAllWindows()
Key Functions and Concepts in Client Code
- client_socket.connect(('localhost', 12345)):
- Connects the client socket to the server at localhost on port 12345.
2. cv2.VideoCapture(video_path):
- Opens the video file specified by video_path.
3. cap.get(cv2.CAP_PROP_FPS):
- Retrieves the frames per second (FPS) of the video.
4. cap.read():
- Reads the next frame from the video. Returns ret (a boolean indicating success) and frame (the actual frame).
5. cv2.resize(frame, (640, 480)):
- Resizes the frame to 640x480 pixels.
6. cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80]):
- Encodes the frame as a JPEG image with 80% quality.
7. pickle.dumps(buffer):
- Serializes the encoded frame data into a byte stream.
8. struct.pack("I", len(data)):
- Packs the length of the serialized frame data into a 4-byte unsigned integer.
9. client_socket.sendall(message_size + data):
- Sends the frame size followed by the frame data to the server.
Conclusion
This tutorial demonstrated how to send and receive video frames over a network using Python sockets. The server receives and displays the frames, while the client reads frames from a video file, sends them to the server, and displays them locally. By understanding the key functions and concepts, you can build more complex video streaming applications.
'Backend Technologies' 카테고리의 다른 글
socket_002: Real-Time Camera Streaming with Sockets (0) | 2025.01.30 |
---|---|
socket_001: Introduction to Sockets in Python (0) | 2025.01.30 |
Connecting Docker Containers for Data Transfer_01: A Step-by-Step Tutorial (0) | 2024.12.26 |
프로토콜 버퍼_01: 프로토콜 버퍼 기초 시작하기 (2) | 2024.12.22 |
Protocol Buffers_01: Getting Started with Protobuf Basics (English Version) (2) | 2024.12.22 |