前言:
????????由于項目需要,最近開始開坑關(guān)于ESP32-CAM系列的RTSP網(wǎng)絡(luò)攝像頭系列,該文章為該系列的第一篇文章。用于記錄項目開發(fā)過程。
本文解決的問題:
? ? ? ? 使用ESP32-CAM獲取圖像數(shù)據(jù),并通過RTSP協(xié)議將獲取到的視頻流傳輸?shù)缴衔粰C進行顯示。
具體實現(xiàn):
????????使用ESP32-CAM進行視頻推流,python端作為rtsp拉流,其中ESP32-CAM使用arduinoIDE開發(fā),使用了安信可的支持庫。支持包安裝網(wǎng)址:
拉流效果:
一、推流部分
官方示例代碼:
#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推流安信可官方已經(jīng)給出了相應(yīng)的示例代碼,改代碼使用宏定義的方式區(qū)分http和rtsp協(xié)議的不同代碼。由于我們不需要用到基于http協(xié)議的視頻推流,因此可以刪去官方代碼中不必要的部分。修改完的代碼如下:
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串口監(jiān)視器輸出的初始化信息,我們需要將ESP32的IP地址安裝RTSP協(xié)議推流的格式填入Python拉流代碼中。
# RTSP 地址 rtsp_url = "rtsp://192.168.168.238:8554/mjpeg/2"
?二、拉流部分
? ? ? ? 由于Opencv-python集成了RTSP協(xié)議拉流的庫函數(shù),因此我們需要下載Opencv-python的支持包??梢源蜷_Pycharm的Terminal使用pip指令快速下載。文章來源:http://www.zghlxwxcb.cn/news/detail-512352.html
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()
# 循環(huán)讀取視頻幀
while True:
# 讀取視頻幀
ret, frame = cap.read()
# 檢查是否成功讀取視頻幀
if not ret:
break
# 顯示視頻幀
cv2.imshow("RTSP Stream", frame)
# 按 'q' 鍵退出循環(huán)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 釋放資源
cap.release()
cv2.destroyAllWindows()
PS:需要注意的是,進行RTSP拉流的上位機和推流的下位機都需要位于同一個局域網(wǎng)下才能進行推拉流傳輸。文章來源地址http://www.zghlxwxcb.cn/news/detail-512352.html
到了這里,關(guān)于ESP32-CAM網(wǎng)絡(luò)攝像頭系列-01-基于RTSP協(xié)議的局域網(wǎng)視頻推流/拉流的簡單實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!