如何用4G模块与云服务器(公网)实现实时图传并保存视频内容
本文介绍了基于USB 4G模块的视频传输方案,包含硬件配置和服务端/客户端代码实现。硬件采用免驱动USB 4G模块连接云服务器,服务端代码使用Python搭建TCP服务器(9090端口),接收1280×720分辨率、10fps的视频帧并保存为5秒MP4片段,具备帧率校准、异常恢复等功能;客户端代码实现视频采集、压缩和传输功能,支持断线重连和质量调节。该系统专为4G网络环境优化,实现了流畅的720P
一、硬件基础

USB 4G模块(不需要安装驱动)
购买链接:https://detail.tmall.com/item.htm?id=788712249477
只需要将SIM卡插到模块上,插入设备USB口,并且需要关闭设备WI-FI。(该设备的连接类似于有线网络的连接)。
注意:本文使用的都是TCP协议,因为博主在用UDP传输未能实现。
二、云服务器平台
阿里云服务器(2核2GB)
三、服务端接收代码
import socket
import cv2
import numpy as np
import time
from datetime import datetime
import os
# -------------------------- 720P配置项 --------------------------
LISTEN_TCP_PORT = 9090 # TCP端口(与发送端需要保持一致)
FRAME_RATE = 10 # 帧率(保持10fps流畅性)
VIDEO_DURATION = 5 # 保存5秒视频
SAVE_PATH = "/root/tcp_video/" # 视频的保存路径
BUFFER_SIZE = 8192 # 增大缓冲区(适配720P更大帧数据)
FRAME_WIDTH = 1280 # 720P宽度
FRAME_HEIGHT = 720 # 720P高度
# -------------------------------------------------------------------
def init_save_dir():
if not os.path.exists(SAVE_PATH):
os.makedirs(SAVE_PATH)
print(f"已创建720P视频保存目录:{SAVE_PATH}")
else:
print(f"720P视频保存目录:{SAVE_PATH}")
def tcp_receiver():
init_save_dir()
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(("0.0.0.0", LISTEN_TCP_PORT))
server_sock.listen(5)
frame_buffer = []
max_frames = FRAME_RATE * VIDEO_DURATION # 10fps*5s=50帧(理论最大帧数)
last_save_time = time.time()
total_frame_count = 0
last_frame_time = None # 记录上一帧接收时间(用于帧率校准)
print(f"云服务器TCP接收端(720P)已启动")
print(f"监听地址:0.0.0.0:{LISTEN_TCP_PORT}(TCP协议)")
print(f"配置:720P(1280×720) | 10fps | 保存5秒视频")
print(f"等待4G端连接...(按 Ctrl+C 退出)")
try:
while True:
client_sock, client_addr = server_sock.accept()
print(f"\n4G端已连接:{client_addr[0]}:{client_addr[1]}")
client_sock.settimeout(10)
try:
while True:
# 接收帧长度(4字节)
len_data = b""
while len(len_data) < 4:
chunk = client_sock.recv(4 - len(len_data))
if not chunk:
print(f" 4G端断开连接(未收到帧长度)")
break
len_data += chunk
if len(len_data) != 4:
break
frame_len = int.from_bytes(len_data, byteorder='big')
# 接收720P帧数据(更大缓冲区)
img_bytes = b""
while len(img_bytes) < frame_len:
chunk = client_sock.recv(min(BUFFER_SIZE, frame_len - len(img_bytes)))
if not chunk:
print(f" 4G端断开连接(未收到完整帧)")
break
img_bytes += chunk
if len(img_bytes) != frame_len:
break
# 解码帧数据
img_np = np.frombuffer(img_bytes, dtype=np.uint8)
frame = cv2.imdecode(img_np, cv2.IMREAD_COLOR)
if frame is None:
print(f" 帧解码失败,跳过该帧")
continue
# 验证720P分辨率
actual_width = frame.shape[1]
actual_height = frame.shape[0]
if actual_width != FRAME_WIDTH or actual_height != FRAME_HEIGHT:
print(f" 分辨率不匹配:实际{actual_width}×{actual_height},预期{FRAME_WIDTH}×{FRAME_HEIGHT}")
# 记录当前帧接收时间(用于帧率校准)
current_frame_time = time.time()
if last_frame_time is not None:
actual_frame_interval = current_frame_time - last_frame_time
# 打印实际帧率(用于调试)
last_frame_time = current_frame_time
# 帧缓存与统计
total_frame_count += 1
frame_buffer.append(frame)
print(
f" 接收第{total_frame_count}帧 | 大小:{frame_len // 1024}KB | 缓存:{len(frame_buffer)}/{max_frames}")
# 严格控制缓存大小(只保留最新的max_frames帧)
if len(frame_buffer) > max_frames:
frame_buffer.pop(0)
# 只在5秒计时到且缓存足够时保存,避免重复保存
current_time = time.time()
if current_time - last_save_time >= VIDEO_DURATION:
if len(frame_buffer) >= max_frames * 0.7: # 至少有70%的有效帧才保存
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
video_path = f"{SAVE_PATH}tcp_720p_{timestamp}.mp4"
# 计算实际帧率(基于缓存中首帧和末帧的时间差)
if len(frame_buffer) >= 2:
buffer_time_diff = last_frame_time - (
current_frame_time - (current_frame_time - last_frame_time) * len(
frame_buffer))
actual_save_fps = len(frame_buffer) / max(buffer_time_diff, 0.1) # 避免除零
actual_save_fps = min(actual_save_fps, FRAME_RATE * 1.5) # 限制最大帧率,防止异常
else:
actual_save_fps = FRAME_RATE
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video_writer = cv2.VideoWriter(
video_path, fourcc, actual_save_fps, (actual_width, actual_height)
)
# 写入所有缓存帧(确保完整5秒内容)
for f in frame_buffer:
video_writer.write(f)
video_writer.release()
print(f"720P视频保存成功:{video_path}")
print(
f" - 帧数:{len(frame_buffer)} | 实际帧率:{actual_save_fps:.1f}fps | 时长:{len(frame_buffer) / actual_save_fps:.1f}s")
# 保存后清空缓存,避免重复写入
frame_buffer.clear()
last_save_time = current_time
else:
print(f" 5秒计时到,但有效帧不足(仅{len(frame_buffer)}/{max_frames}),跳过保存")
# 不清空缓存,继续累积帧
last_save_time = current_time # 重置计时,避免频繁提示
except socket.timeout:
print(f" 连接超时(10秒无数据)")
except Exception as e:
print(f" 连接异常:{str(e)}")
finally:
client_sock.close()
print(f"与4G端 {client_addr[0]} 的连接已关闭")
frame_buffer.clear()
last_save_time = time.time()
last_frame_time = None
except KeyboardInterrupt:
print("\n 收到退出指令,正在清理资源...")
if frame_buffer and len(frame_buffer) >= 5: # 至少5帧才保存退出视频
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
exit_video_path = f"{SAVE_PATH}tcp_720p_exit_{timestamp}.mp4"
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
# 用实际帧率保存退出视频
if len(frame_buffer) >= 2 and last_frame_time is not None:
first_frame_time = last_frame_time - (last_frame_time - (last_frame_time - (
current_frame_time if 'current_frame_time' in locals() else last_frame_time)) * len(frame_buffer))
buffer_time_diff = last_frame_time - first_frame_time
actual_save_fps = len(frame_buffer) / max(buffer_time_diff, 0.1)
else:
actual_save_fps = FRAME_RATE
actual_width = frame_buffer[0].shape[1] if frame_buffer else FRAME_WIDTH
actual_height = frame_buffer[0].shape[0] if frame_buffer else FRAME_HEIGHT
video_writer = cv2.VideoWriter(
exit_video_path, fourcc, actual_save_fps, (actual_width, actual_height)
)
for f in frame_buffer:
video_writer.write(f)
video_writer.release()
print(f"退出时保存缓存帧:{exit_video_path}({len(frame_buffer)}帧 | {actual_save_fps:.1f}fps)")
finally:
server_sock.close()
print("\n 接收端已完全关闭")
print(f" 接收统计:总接收帧数 {total_frame_count}")
if __name__ == "__main__":
tcp_receiver()
该代码实现了一个基于TCP协议的720P视频接收服务端,监听9090端口等待4G端连接,接收分辨率为1280×720、帧率10fps的视频帧,通过缓冲区缓存最新5秒(50帧)的有效帧,每满5秒且缓存帧达到70%以上时,以实际帧率生成MP4格式视频并保存到指定目录,支持连接超时处理、异常恢复、退出时缓存帧保存及接收统计,同时包含分辨率验证、帧率校准等调试与适配机制。
四、4G发送端代码
import socket
import cv2
import numpy as np
import time
# -------------------------- 核心配置(与接收端一致)--------------------------
SERVER_IP = " " # 替换为实际服务器IP
SERVER_TCP_PORT = 9090 #端口号
VIDEO_SOURCE = 0 # 0=摄像头,本地视频填路径(如"test.mp4")
FRAME_RATE = 10
FRAME_WIDTH = 1280
FRAME_HEIGHT = 720
JPG_QUALITY = 40 # 带宽不够就降到30,清晰度不够就升到50
BUFFER_SIZE = 8192
CONNECT_TIMEOUT = 10
RECONNECT_INTERVAL = 3
# -------------------------------------------------------------------
def create_socket():
"""简化socket创建,避免配置冲突"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(CONNECT_TIMEOUT)
return sock
def tcp_sender():
print(f" 4G端发送端启动 | 连接 {SERVER_IP}:{SERVER_TCP_PORT}")
print(f" 配置:{FRAME_WIDTH}x{FRAME_HEIGHT} | {FRAME_RATE}fps | JPG{JPG_QUALITY}%")
print("操作:按'q'退出 | 'p'暂停/恢复")
# 初始化视频捕获(兼容更多设备)
cap = cv2.VideoCapture(VIDEO_SOURCE)
if not cap.isOpened():
print(f" 无法打开视频源!检查摄像头权限或文件路径")
return
# 简化分辨率设置(避免设备不支持)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)
cap.set(cv2.CAP_PROP_FPS, FRAME_RATE)
actual_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
actual_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f" 实际分辨率:{actual_w}x{actual_h}(需与接收端一致)")
sock = None
frame_count = 0
send_ok = 0
pause = False
target_interval = 1.0 / FRAME_RATE # 稳定帧率用
try:
# 初始连接
sock = create_socket()
sock.connect((SERVER_IP, SERVER_TCP_PORT))
sock.settimeout(None)
print(" 连接成功!开始发送")
while True:
# 键盘控制
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
if key == ord('p'):
pause = not pause
print(f" {'暂停' if pause else '恢复'}")
time.sleep(0.5)
if pause:
continue
# 控制帧率(避免发送过快)
start_time = time.time()
# 读取并处理帧
ret, frame = cap.read()
if not ret:
print(" 视频源断开")
break
# 强制Resize到目标分辨率(关键!避免接收端不匹配)
frame = cv2.resize(frame, (FRAME_WIDTH, FRAME_HEIGHT))
# 压缩帧(简化参数,避免编码失败)
ret, img_encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, JPG_QUALITY])
if not ret:
print(" 帧压缩失败,跳过")
continue
img_bytes = img_encoded.tobytes()
frame_len = len(img_bytes).to_bytes(4, byteorder='big')
# 发送数据(简化分片,避免粘包)
try:
# 先发送长度,再发送数据
sock.sendall(frame_len)
sock.sendall(img_bytes)
send_ok += 1
except (ConnectionResetError, BrokenPipeError):
print(" 连接断开,尝试重连...")
time.sleep(RECONNECT_INTERVAL)
sock = create_socket()
sock.connect((SERVER_IP, SERVER_TCP_PORT))
sock.settimeout(None)
print(" 重连成功")
except Exception as e:
print(f" 发送失败:{str(e)}")
# 统计信息
frame_count += 1
if frame_count % 10 == 0:
fps = frame_count / (time.time() - start_time)
print(f" 已发{frame_count}帧 | 成功{send_ok}帧 | 单帧{len(img_bytes)//1024}KB | FPS{fps:.1f}")
# 本地预览
cv2.putText(frame, f"Send: {send_ok}/{frame_count}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
cv2.imshow("Sender", frame)
# 稳定帧率(避免CPU占用过高)
elapsed = time.time() - start_time
if elapsed < target_interval:
time.sleep(target_interval - elapsed)
except socket.timeout:
print(f" 连接超时({CONNECT_TIMEOUT}秒)")
except ConnectionRefusedError:
print(f" 连接被拒绝(服务器未启动或端口未开放)")
except Exception as e:
print(f" 程序异常:{str(e)}")
finally:
cap.release()
cv2.destroyAllWindows()
if sock:
sock.close()
print(f"\n 发送端关闭 | 总帧{frame_count} | 成功{send_ok} | 成功率{send_ok/frame_count*100:.1f}%" if frame_count else "未发送任何帧")
if __name__ == "__main__":
tcp_sender()
发送端实现了一个TCP协议的720P视频发送,需配置目标服务器IP与9090端口,支持摄像头或本地视频作为数据源,按10fps帧率、1280×720分辨率(强制Resize适配)、可调节的JPG压缩质量传输视频帧,具备连接重连、暂停/恢复发送、本地预览、帧率稳定控制及发送统计功能,通过先传帧长度再传帧数据的方式避免粘包,适配4G网络场景下的视频传输需求。
更多推荐
所有评论(0)