使用UDP协议传输视频流!(分片、缓存)

news2024/11/8 12:46:14

背景

最近在开发工作中遇到需要两台本地设备之间进行视频流的传输的情况。但是团队一来没有这方面的专业人才,二来视频流的传续数据量很大,针对TCP和UDP的具体选择也不明确。

本文是在上诉背景之下进行的研究和开发工作。

目录

背景

UDP和TCP协议的选择

Socket-UDP协议代码详解

UDP协议发送端

UDP协议接收端


UDP和TCP协议的选择

视频流更加适合UDP协议的传输!

序号UDP协议TCP协议
是否需要握手不需要握手需要握手
是否确保数据帧传输准确性不确保确保
是否确保数据帧传输顺序一致性不确保确保
速度相对高速相对低速

对于一些需要准确传输的信息,则选择TCP协议

对于一些需要高速传输和不在意准确性的数据,选择UDP协议。

显然实时视频流就是一个典型的适合UDP协议的数据。

  • 实时视频流不在意数据是否完整传输(因为传输错误的帧马上就是过去式了,接着显示新的帧)
  • 实时视频流不在意帧是否顺序一致(少数的几帧在短暂的时间戳内顺序不一致无伤大雅)
  • 但实时视频流需要帧高速

Socket-UDP协议代码详解

UDP协议发送端

是否常常遇到问题:

OSError: [WinError 10040] 一个在数据报套接字上发送的消息大于内部消息缓冲区或其他一些网络限制,或该用户用于接收数据报的缓冲区比数据报小?

简单,发送端仅需要分片发送即可,当然接收端也需要想要调整。

import cv2
import socket
import time
import struct
import numpy as np
import logging

# 配置摄像头和UDP传输参数
FPS_INTERVAL = 0.1  # 每隔0.1秒计算一次帧率
UDP_IP = "127.0.0.1"  # 目标接收端IP
UDP_PORT = 12345  # 目标接收端端口
MAX_UDP_SIZE = 1024  # 每个数据包最大传输大小,调整为1024字节

# 设置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger()

# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 打开摄像头
cap = cv2.VideoCapture(1)
if not cap.isOpened():
    logger.error("无法打开摄像头,请检查设备连接")
    exit(1)

cap.set(cv2.CAP_PROP_FRAME_WIDTH, 4000)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 3000)
cap.set(cv2.CAP_PROP_FPS, 60)

# 初始化一些变量
frame_count = 0
last_time = time.time()

while True:
    try:
        ret, frame = cap.read()
        if not ret:
            logger.warning("无法读取摄像头帧")
            continue  # 如果读取失败,跳过本次循环

        # 定义新的大小(宽度,高度)
        new_dims = (1280, 960)  # 新的宽度和高度
        # 使用cv2.resize()调整图像大小
        frame = cv2.resize(frame, new_dims, interpolation=cv2.INTER_LINEAR)

        # 将帧转换为JPEG格式
        ret, jpeg = cv2.imencode('.jpg', frame)
        if ret:
            # 将JPEG图像数据转为字节流
            data = jpeg.tobytes()
            data_len = len(data)

            # 发送帧数据的总长度
            try:
                sock.sendto(struct.pack("L", data_len), (UDP_IP, UDP_PORT))  # 发送数据长度
            except socket.error as e:
                logger.error(f"发送数据长度失败: {e}")
                continue  # 如果发送失败,跳过本次循环

            # 分片发送数据
            for i in range(0, data_len, MAX_UDP_SIZE):
                packet = data[i:i+MAX_UDP_SIZE]
                try:
                    sock.sendto(packet, (UDP_IP, UDP_PORT))  # 发送数据片段
                except socket.error as e:
                    logger.error(f"发送数据片段失败: {e}")
                    continue  # 如果发送失败,跳过本次循环
        
        # 计算帧率:每帧计算一次
        current_time = time.time()
        frame_time = current_time - last_time  # 计算当前帧的时间差
        fps = 1.0 / frame_time if frame_time > 0 else 0  # 帧率 = 1 / 帧间隔

        # 更新上次帧的时间
        last_time = current_time

        # 在左上角显示帧率
        cv2.putText(frame, f"client-FPS: {fps:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

        # 显示视频流
        cv2.imshow('Camera', frame)

    except (cv2.error, socket.error) as e:
        logger.error(f"发生异常: {e}")
        # 如果发生异常,等待一段时间重试
        time.sleep(2)
        continue

    # 按'q'退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()

UDP协议接收端

在遇到缓存问题的时候,接收端除了增设分片接受以外,还需要进行缓冲区大小的设定,这里推荐为5MB。当然还需要try except之后清空所有的缓冲区!

import cv2
import socket
import struct
import numpy as np
import time
import logging

# 配置UDP接收参数
UDP_IP = "127.0.0.1"  # 本地IP
UDP_PORT = 12345  # 端口号

# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

# 增加接收缓冲区的大小
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1048576 * 5)  # 设置接收缓冲区大小为5MB

# 用于接收数据的缓冲区
buffer = b''

# 设置日志记录
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename='udp_server.log', level=logging.DEBUG, format=LOG_FORMAT)

last_time = time.time()

def clear_socket_buffer():
    """
    清空socket的接收缓冲区,丢弃所有未处理的数据。
    """
    while True:
        # 尝试读取一部分数据
        sock.settimeout(0.1)  # 设置一个短暂的超时避免阻塞
        try:
            data = sock.recv(4096)  # 尝试读取最大4KB的数据
            if not data:
                break
        except socket.timeout:
            break  # 如果超时,退出循环

while True:
    try:
        # 接收数据长度(最多接收4字节)
        data_len, addr = sock.recvfrom(4)
        if not data_len:
            continue
        
        data_len = struct.unpack("L", data_len)[0]
        
        # 接收图像数据(分片)
        buffer = b''  # 清空之前的缓冲区
        while len(buffer) < data_len:
            packet, addr = sock.recvfrom(1450)  # 每次接收一个片段
            buffer += packet  # 将接收到的数据片段拼接到缓冲区

        # 确保接收到完整数据
        if len(buffer) == data_len:
            # 解码图像
            nparr = np.frombuffer(buffer, np.uint8)
            frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

            if frame is not None:
                # 计算并显示帧率
                fps = 1 / (time.time() - last_time) if (time.time() - last_time) > 0 else 0
                last_time = time.time()

                # 在左上角显示帧率
                cv2.putText(frame, f"Server-FPS: {fps:.2f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
                
                # 显示接收到的图像
                cv2.imshow('Received Video Stream', frame)

            else:
                logging.warning("接收到的图像无法解码!")
                continue  # 如果解码失败,跳过本次循环

        else:
            logging.error(f"接收到的数据包大小不匹配: 期望 {data_len} 字节, 实际 {len(buffer)} 字节")
            continue  # 如果数据不完整,跳过本次循环

    except socket.timeout:
        logging.warning("接收超时,等待下一帧数据...")
        continue  # 如果超时,继续等待

    except Exception as e:
        clear_socket_buffer()
        logging.error(f"发生异常: {e}")
        time.sleep(1)  # 如果发生异常,休眠2秒后继续尝试

    # 按 'q' 键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放资源
cv2.destroyAllWindows()
sock.close()
logging.info("服务端退出,释放资源")

其实直接拿去用即可! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2235826.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot】18 上传文件到数据库(Thymeleaf + MySQL)

Git仓库 https://gitee.com/Lin_DH/system 介绍 使用 Thymeleaf 写的页面&#xff0c;将&#xff08;txt、jpg、png&#xff09;格式文件上传到 MySQL 数据库中。 依赖 pom.xml <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --><depende…

[VUE]框架网页开发1 本地开发环境安装

前言 其实你不要看我的文章比较长&#xff0c;但是他就是很长&#xff01;步骤其实很简单&#xff0c;主要是为新手加了很多解释&#xff01; 步骤一&#xff1a;下载并安装 Node.js 访问 Node.js 官网&#xff1a; Node.js — Download Node.js 下载 Windows 64 位版本&…

canal1.1.7使用canal-adapter进行mysql同步数据

重要的事情说前面&#xff0c;canal1.1.8需要jdk11以上&#xff0c;大家自行选择&#xff0c;我这由于项目原因只能使用1.1.7兼容版的 文章参考地址&#xff1a; canal 使用详解_canal使用-CSDN博客 使用canal.deployer-1.1.7和canal.adapter-1.1.7实现mysql数据同步_mysql更…

403 Request Entity Too Lager(请求体太大啦)

昨天收到 QA 的生产报障&#xff0c;说是测试环境的附件上传功能报了 403 的错误&#xff0c;错误信息&#xff1a;403 Request Entity Too Lager。我尝试复现问题&#xff0c;发现传个几兆的文件都费劲啊&#xff0c;一传一个失败。不用说&#xff0c;项目用到 ng 代理&#x…

【VScode】如何在VSCode中配置Python开发环境:从零开始的完整指南

文章目录 前言软件准备软件安装1. 安装Python2. 检查Python是否安装成功3. 安装第三方包4. 安装VSCode 配置VSCode1. 安装Python插件2. 创建项目&#xff0c;配置工作区域3. 编写Python文件4. 配置Python编译设置5. 使用代码格式化工具yapf 更多文章结尾 前言 在当今的编程世界…

SQL,力扣题目571, 给定数字的频率查询中位数

一、力扣链接 LeetCode_571 二、题目描述 Numbers 表&#xff1a; ------------------- | Column Name | Type | ------------------- | num | int | | frequency | int | ------------------- num 是这张表的主键(具有唯一值的列)。 这张表的每一行表示某个数…

LLMs之PDF:zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略

LLMs之PDF&#xff1a;zeroX(一款PDF到Markdown 的视觉模型转换工具)的简介、安装和使用方法、案例应用之详细攻略 目录 zeroX的简介 1、支持的文件类型 zeroX的安装和使用方法 T1、Node.js 版本&#xff1a; 安装 使用方法 使用文件 URL&#xff1a; 使用本地路径&…

containerd配置私有仓库registry

机器ip端口regtisry192.168.0.725000k8s-*-------k8s集群 1、镜像上传 rootadmin:~# docker push 192.168.0.72:5000/nginx:1.26.1-alpine The push refers to repository [192.168.0.72:5000/nginx] 6961f0b8531c: Pushed 3112cd521249: Pushed d3f50ce9b5b5: Pushed 9efaf2eb…

js例轮播图定时器版

要求 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevice-width, ini…

PostgreSQL 学习笔记:PostgreSQL 主从复制

PostgreSQL 笔记&#xff1a;PostgreSQL 主从复制 博客地址&#xff1a;TMDOG 的博客 在现代应用程序中&#xff0c;数据库的高可用性和扩展性是至关重要的。PostgreSQL 提供了主从复制功能&#xff0c;可以在多个数据库实例之间复制数据&#xff0c;以实现冗余和负载均衡。本…

【系统集成项目管理工程师教程】第5章 软件工程

软件工程是一门研究用工程化方法构建和维护有效、实用和高质量软件的学科&#xff0c;涵盖软件需求、设计、实现、测试、部署交付、质量管理和过程能力成熟度等方面&#xff0c;旨在提高软件生产率、质量并降低成本&#xff0c;确保软件项目的成功开发与维护。 5.1软件工程定义…

Java项目实战II基于Spring Boot的便利店信息管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在快节奏的…

vscode Comment Translate 反应慢 加载中...

Comment Translate 版本&#xff1a;v2.3.3 你是不是疑惑切换了 Bing 源也无法使用还是加载中… 那么可能你切换Bing后没重启vscode 下面是切换成功后的插件日志&#xff0c;一定要重启vscode&#xff0c;只是禁用和启用插件不行的&#xff0c;另外google是没用的&#xff0c;用…

网站架构知识之Ansible(day020)

1.Ansible架构 Inventory 主机清单:被管理主机的ip列表,分类 ad-hoc模式: 命令行批量管理(使用ans模块),临时任务 playbook 剧本模式: 类似于把操作写出脚本,可以重复运行这个脚本 2.修改配置 配置文件&#xff1a;/etc/ansible/ansible.cfg 修改配置文件关闭主机Host_key…

智启未来,趣享生活 德国卡赫举办系列新品首发活动

全球最大的清洁设备和清洁解决方案提供商德国卡赫&#xff0c;于11月6日在第七届进博会新品发布平台举办主题为“智启未来&#xff0c;趣享生活”的新品发布会&#xff0c;揭开全球首发新品可折叠式手持清洗机KHB Air以及亚洲首发新品商用清洁机器人KIRA CV 50的神秘面纱。作为…

xlsx.js 读取excel文件

需求&#xff1a;读取一个excel文件。 一、 使用antd的Upload组件的 【customRequest】方法。 互斥。此方法跟【onChange】方法互斥&#xff0c;即&#xff1a;不可同时出现。调用次数不一样。onChange方法会根据文件当前的上传状态从而被调用多次&#xff08;读取中&#xff…

WPF中的依赖属性

1.创建项目后下载两个NuGet程序包 2.创建一个MyButton类继承Button MyButton类如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Contro…

Java执行顺序大揭秘:静态块、非静态块和构造方法谁先谁后?

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hello大家好,我是小米!今天咱们来聊聊Java中的一个小而重要的知识点——初始化块。可能有的小伙伴一听这名字就觉得有点头大,这东西到底是干嘛的?为…

【Linux系列】字符串操作的艺术:删除前缀的 Shell 脚本技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

nginx 部署2个相同的vue

起因&#xff1a; 最近遇到一个问题&#xff0c;在前端用nginx 部署 vue&#xff0c; 发现如果前端有改动&#xff0c;如果不适用热更新&#xff0c;而是直接复制项目过去&#xff0c;会404 因此想到用nginx 负载两套相同vue项目&#xff0c;然后一个个复制vue项目就可以了。…