ESP32-CAM网络摄像头系列-01-基于RTSP协议的局域网视频推流/拉流的简单实现

2023-11-07

前言:

        由于项目需要,最近开始开坑关于ESP32-CAM系列的RTSP网络摄像头系列,该文章为该系列的第一篇文章。用于记录项目开发过程。

本文解决的问题:

        使用ESP32-CAM获取图像数据,并通过RTSP协议将获取到的视频流传输到上位机进行显示。

具体实现:

        使用ESP32-CAM进行视频推流,python端作为rtsp拉流,其中ESP32-CAM使用arduinoIDE开发,使用了安信可的支持库。支持包安装网址:

拉流效果:

一、推流部分

官方示例代码:

#include "OV2640.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>

#include "SimStreamer.h"
#include "OV2640Streamer.h"
#include "CRtspSession.h"

#define ENABLE_RTSPSERVER

OV2640 cam;

#ifdef ENABLE_WEBSERVER
WebServer server(80);
#endif

#ifdef ENABLE_RTSPSERVER
WiFiServer rtspServer(8554);
#endif


#ifdef SOFTAP_MODE
IPAddress apIP = IPAddress(192, 168, 1, 1);
#else
#include "wifikeys_template.h"
#endif

#ifdef ENABLE_WEBSERVER
void handle_jpg_stream(void)
{
    WiFiClient client = server.client();
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
    server.sendContent(response);

    while (1)
    {
        cam.run();
        if (!client.connected())
            break;
        response = "--frame\r\n";
        response += "Content-Type: image/jpeg\r\n\r\n";
        server.sendContent(response);

        client.write((char *)cam.getfb(), cam.getSize());
        server.sendContent("\r\n");
        if (!client.connected())
            break;
    }
}

void handle_jpg(void)
{
    WiFiClient client = server.client();

    cam.run();
    if (!client.connected())
    {
        return;
    }
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-disposition: inline; filename=capture.jpg\r\n";
    response += "Content-type: image/jpeg\r\n\r\n";
    server.sendContent(response);
    client.write((char *)cam.getfb(), cam.getSize());
}

void handleNotFound()
{
    String message = "Server is running!\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    server.send(200, "text/plain", message);
}
#endif

#ifdef ENABLE_OLED
#define LCD_MESSAGE(msg) lcdMessage(msg)
#else
#define LCD_MESSAGE(msg)
#endif

#ifdef ENABLE_OLED
void lcdMessage(String msg)
{
    if(hasDisplay) {
        display.clear();
        display.drawString(128 / 2, 32 / 2, msg);
        display.display();
    }
}
#endif

CStreamer *streamer;

void setup()
{
  #ifdef ENABLE_OLED
    hasDisplay = display.init();
    if(hasDisplay) {
        display.flipScreenVertically();
        display.setFont(ArialMT_Plain_16);
        display.setTextAlignment(TEXT_ALIGN_CENTER);
    }
  #endif
    LCD_MESSAGE("booting");

    Serial.begin(115200);
    while (!Serial)
    {
        ;
    }
    cam.init(esp32cam_aithinker_config);

    IPAddress ip;


#ifdef SOFTAP_MODE
    const char *hostname = "devcam";
    // WiFi.hostname(hostname); // FIXME - find out why undefined
    LCD_MESSAGE("starting softAP");
    WiFi.mode(WIFI_AP);
    WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
    bool result = WiFi.softAP(hostname, "12345678", 1, 0);
    if (!result)
    {
        Serial.println("AP Config failed.");
        return;
    }
    else
    {
        Serial.println("AP Config Success.");
        Serial.print("AP MAC: ");
        Serial.println(WiFi.softAPmacAddress());

        ip = WiFi.softAPIP();
    }
#else
    LCD_MESSAGE(String("join ") + ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(F("."));
    }
    ip = WiFi.localIP();
    Serial.println(F("WiFi connected"));
    Serial.println("");
    Serial.println(ip);
#endif

    LCD_MESSAGE(ip.toString());

#ifdef ENABLE_WEBSERVER
    server.on("/", HTTP_GET, handle_jpg_stream);
    server.on("/jpg", HTTP_GET, handle_jpg);
    server.onNotFound(handleNotFound);
    server.begin();
#endif

#ifdef ENABLE_RTSPSERVER
    rtspServer.begin();

    //streamer = new SimStreamer(true);             // our streamer for UDP/TCP based RTP transport
    streamer = new OV2640Streamer(cam);             // our streamer for UDP/TCP based RTP transport
#endif
}

void loop()
{
#ifdef ENABLE_WEBSERVER
    server.handleClient();
#endif

#ifdef ENABLE_RTSPSERVER
    uint32_t msecPerFrame = 100;
    static uint32_t lastimage = millis();

    // If we have an active client connection, just service that until gone
    streamer->handleRequests(0); // we don't use a timeout here,
    // instead we send only if we have new enough frames
    uint32_t now = millis();
    if(streamer->anySessions()) {
        if(now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover
            streamer->streamImage(now);
            lastimage = now;

            // check if we are overrunning our max frame rate
            now = millis();
            if(now > lastimage + msecPerFrame) {
                printf("warning exceeding max frame rate of %d ms\n", now - lastimage);
            }
        }
    }
    
    WiFiClient rtspClient = rtspServer.accept();
    if(rtspClient) {
        Serial.print("client: ");
        Serial.print(rtspClient.remoteIP());
        Serial.println();
        streamer->addSession(rtspClient);
    }
#endif
}

        对于ESP32的RTSP推流安信可官方已经给出了相应的示例代码,改代码使用宏定义的方式区分http和rtsp协议的不同代码。由于我们不需要用到基于http协议的视频推流,因此可以删去官方代码中不必要的部分。修改完的代码如下:

ESP32部分的代码由官方示例代码修改而来。只保留RTSP推流部分。

#include "OV2640.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>

#include "SimStreamer.h"
#include "OV2640Streamer.h"
#include "CRtspSession.h"

// copy this file to wifikeys.h and edit
const char *ssid =     "YAN";         // Put your SSID here
const char *password = "qwertyuiop";     // Put your PASSWORD here

#define ENABLE_RTSPSERVER

OV2640 cam;

WiFiServer rtspServer(8554);

CStreamer *streamer;

void setup()
{
    Serial.begin(115200);
    while (!Serial);
    cam.init(esp32cam_aithinker_config);

    IPAddress ip;

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(F("."));
    }
    ip = WiFi.localIP();
    Serial.println(F("WiFi connected"));
    Serial.println("");
    Serial.println(ip);

    rtspServer.begin();

    //streamer = new SimStreamer(true);             // our streamer for UDP/TCP based RTP transport
    streamer = new OV2640Streamer(cam);             // our streamer for UDP/TCP based RTP transport
}

void loop()
{
    uint32_t msecPerFrame = 100;
    static uint32_t lastimage = millis();

    // If we have an active client connection, just service that until gone
    streamer->handleRequests(0); // we don't use a timeout here,
    // instead we send only if we have new enough frames
    uint32_t now = millis();
    if(streamer->anySessions()) {
        if(now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover
            streamer->streamImage(now);
            lastimage = now;

            // check if we are overrunning our max frame rate
            now = millis();
            if(now > lastimage + msecPerFrame) {
                printf("warning exceeding max frame rate of %d ms\n", now - lastimage);
            }
        }
    }
    
    WiFiClient rtspClient = rtspServer.accept();
    if(rtspClient) {
        Serial.print("client: ");
        Serial.print(rtspClient.remoteIP());
        Serial.println();
        streamer->addSession(rtspClient);
    }
}

ArduinoIDE串口监视器输出的初始化信息,我们需要将ESP32的IP地址安装RTSP协议推流的格式填入Python拉流代码中。

# RTSP 地址
rtsp_url = "rtsp://192.168.168.238:8554/mjpeg/2"

 二、拉流部分

        由于Opencv-python集成了RTSP协议拉流的库函数,因此我们需要下载Opencv-python的支持包。可以打开Pycharm的Terminal使用pip指令快速下载。

pip install opencv-python

 上位机python3拉流代码:

import cv2

# RTSP 地址
rtsp_url = "rtsp://192.168.168.238:8554/mjpeg/2"

# 打开 RTSP 视频流
cap = cv2.VideoCapture(rtsp_url)

# 检查视频是否成功打开
if not cap.isOpened():
    print("Failed to open RTSP stream")
    exit()

# 循环读取视频帧
while True:
    # 读取视频帧
    ret, frame = cap.read()

    # 检查是否成功读取视频帧
    if not ret:
        break

    # 显示视频帧
    cv2.imshow("RTSP Stream", frame)

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

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

PS:需要注意的是,进行RTSP拉流的上位机和推流的下位机都需要位于同一个局域网下才能进行推拉流传输。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ESP32-CAM网络摄像头系列-01-基于RTSP协议的局域网视频推流/拉流的简单实现 的相关文章

随机推荐

  • python类型提示

    一 类型提示的好处 1 增强代码的可读性 2 IDE中代码提示 3 静态代码检查 第三方库 二 IDE中代码中提示功能 1 为参数与返回数据指定类型 def greeting name str gt str gt str 返回值也可以 re
  • 希波克拉底誓言——一个医生的职业道德准则

    本文转载至 http cd qq com a 20071207 000170 htm 希波克拉底誓言是2400年以前写的 大约和我们的孔子同一个时代 这个誓言总共只有五百多个字 按中文计 但是产生的影响却非常深远 至今 几乎所有学医学的学生
  • 深度解密 5 类大数据架构及实现

    前几天读到白发川的一篇文章 对比解读五种主流大数据架构的数据分析能力 文中详细总结了各类数据架构的应用以及原理 作为一名在数据仓库耕耘多年的技术人员 对于其中的一些技术细节还是破解兴趣的 所以随着作者的思路写下了我对主流数据架构的理解 如无
  • 对于Linux中errno使用的问题

    最近在网络编程使用的过程中 发现errno会经常使用 因此决定在此做个留用 以备以后使用 虽然errno是非线程安全的 但是可以通过几种机制保证其安全 最近在使用的过程中获得了errno 程序无法执行 也不知道如何解决问题 因此 理解每一个
  • springboot如何进行混淆加密(proguard+xjar)

    一 背景 项目组核心代码模块部署于用户服务器上 另外一家公司获取了该服务器的root密码 常规的通过配置环境变量来进行数据库加密处理的方式 直接甩jar包到服务器的方式 极有可能导致数据泄露和代码泄露 二 代码混淆 1 常用的混淆工具 软件
  • NoSQL与关系数据库的比较

    表中给出了NoSQL和关系数据库 Relational DataBase Management System RDBMS 的简单比较 对比指标包括数据库原理 数据规模 数据库模式 查询效率 一致性 数据完整性 扩展性 可用性 标准化 技术支
  • 一站集齐近半年大模型前沿动态

    点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入 大 模 型 LLM 近半年大模型一路狂飙 席卷全球 已经成为了AI领域的研究热点与必争之地 AI TIME大模型系列活动定期特邀来自全球知名高校与研究机构的青年学者 分享最新大模
  • 第十个项目遥感处理cgal+pcl+gdal+opencv+qt+osg(2018年1月开始)

    这个项目是正式入职的第一个公司项目 学的东西很多 每天都在学习新东西 只是和以前的积累有点偏 严格地讲 也不叫偏 以前纯粹是瞎胡搞 API的调用而已 现在业务层次是图像处理 没有硕士学位的人不好弄 提高了门槛 也算是一种保护 免得吃青春饭
  • MongoDB进阶指南!

    想必大家很多人都在业务开发的时候遇到这样的痛点 最近在用数据库存储数据的时候发现这么一个坑 例如从消息队列中监听消息的时候 原来的做法是将监听的消息json数据存储在数据库 以便好对异常消息数据进行追溯 消息内容使用text类型存储 起初因
  • JAVA IO流详解

    File File是java io包下的类 代表与平台无关的文件和目录 File能创建 删除 重命名文件和目录 也能检测 访问文件和目录本身 File不能访问文件中的内容 如果要访问内容 则需要使用输入 输出流 过滤文件 File类的lis
  • Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerExcepti

    本文目录 一 背景描述 二 原因分析 三 解决方案 一 背景描述 项目架构 Spring Boot v2 0 0 RELEASE Mybatis Plus v3 1 1 今天在一个老项目 运行的非常正常 上开发一个新的功能 添加新功能之前
  • anaconda中安装pytorch(GPU版)(离线安装)(最简单)

    本文介绍在anaconda中安装pytorch 最近因为学习需要 要下载pytorchGPU版本来训练网络 相信pytorch大家都不陌生了 PyTorch 是一个 Torch7 团队开源的 Python 优先的深度学习框架 提供两个高级功
  • 华为od最短木板长度

    题目描述 小明有n块木板 第i 1 i n 块木板的长度为ai 小明买了一块长度为m的木料 这块木料可以切割成任意块 拼接到已有的木板上 用来加长木板 小明想让最短的木板尽量长 请问小明加长木板后 最短木板的长度最大可以为多少 输入描述 输
  • 安装、卸载mysql服务命令

    1 dos下用命令 进入mysql的bin目录下 mysqld nt exe install mysql 服务名字 mysqld nt exe remove mysql 服务名字 2 安装卸载mysql服务的bat文件的写法 安装mysql
  • Mac下Flutter环境配置

    最近研究Flutter Flutter环境配置弄了一下午 总算弄好了 所以整理下文章记录分享给大家 如有不全面的地方 还望大家指正 步骤如下 1 首先 下载Flutter SDK 提供两种方式 一 从git下载Flutter https g
  • “unable to find a medium containing a live file system“问题真正有效的解决方法。

    真正有效的关于ubuntu 16 04安装U盘安装出现 unable to find a medium containing a live file system 问题的解决方法 网上搜到的都是乱弹琴 一个靠谱的都没有 真正的解决方法 出现
  • maven的settings.xml,pom.xml配置

    1settings xml
  • 找不到d3dx9_43.dll丢失怎么解决(分享几种解决方法)

    为什么我们打开电脑软件或许游戏时候 电脑会报错出现d3dx9 43 dll丢失 或许找不到d3dx9 43 dll等等的提示 下面来详细介绍一下d3dx9 43 dll详细解决方法跟d3dx9 43 dll是什么 如果你的系统中没有安装或安
  • 【Python】 _tkinter.TclError: bitmap "xzw.ico" not defined

    问题描述 在Python中可以使用pyinstaller命令将 py文件打包成 exe文件 但是成功打包成 exe文件后 在Windows系统上运行却出现了如下错误 tkinter TclError bitmap xzw ico not d
  • ESP32-CAM网络摄像头系列-01-基于RTSP协议的局域网视频推流/拉流的简单实现

    前言 由于项目需要 最近开始开坑关于ESP32 CAM系列的RTSP网络摄像头系列 该文章为该系列的第一篇文章 用于记录项目开发过程 本文解决的问题 使用ESP32 CAM获取图像数据 并通过RTSP协议将获取到的视频流传输到上位机进行显示