一、硬件基础

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网络场景下的视频传输需求。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐