有关esp32-cam目前大多数文章都是在写如何在内网(同一网络环境下)的视频传输, 即便是传输服务器上, 视频图像也只是显示在服务器中, 无法将视频数据流再转发到客户端. 先介绍一下大致思路, 很简单, esp32-cam把数据流发送到公网服务器, 服务器将数据原样透传给用户的电脑端, esp32-cam使用的c语言, 公网服务器端使用nodejs, 而电脑客户端使用了python                                          esp32-cam ==> 公网服务器 ==> 电脑客户端

所有的传输都是在tcp协议下进行的, 所以小伙伴在公网服务器上开端口时记得选择tcp协议. 首先声明下esp32-cam和电脑客户端的代码都是参考了dsxcode这位博主的代码, 以下这两部分的代码也只做转载. 参考连接: ESP32 CAM与服务器(python)TCP视频传输_dsxcode的博客-CSDN博客_esp32 tcp数据传输

上代码, esp32-cam端使用的Arduino c语言编写:


#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <vector>const char *ssid = "xxxx"; //wifi用户名
const char *password = "xxxx"; //wifi密码
const IPAddress serverIP(xxxx);  //你自己的公网服务器ip地址
uint16_t serverPort = xxxx;         //服务器端口号(tcp协议)#define maxcache 1430WiFiClient client; //声明一个客户端对象,用于与服务器进行连接//CAMERA_MODEL_AI_THINKER类型摄像头的引脚定义
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22static camera_config_t camera_config = {.pin_pwdn = PWDN_GPIO_NUM,.pin_reset = RESET_GPIO_NUM,.pin_xclk = XCLK_GPIO_NUM,.pin_sscb_sda = SIOD_GPIO_NUM,.pin_sscb_scl = SIOC_GPIO_NUM,.pin_d7 = Y9_GPIO_NUM,.pin_d6 = Y8_GPIO_NUM,.pin_d5 = Y7_GPIO_NUM,.pin_d4 = Y6_GPIO_NUM,.pin_d3 = Y5_GPIO_NUM,.pin_d2 = Y4_GPIO_NUM,.pin_d1 = Y3_GPIO_NUM,.pin_d0 = Y2_GPIO_NUM,.pin_vsync = VSYNC_GPIO_NUM,.pin_href = HREF_GPIO_NUM,.pin_pclk = PCLK_GPIO_NUM,.xclk_freq_hz = 20000000,.ledc_timer = LEDC_TIMER_0,.ledc_channel = LEDC_CHANNEL_0,.pixel_format = PIXFORMAT_JPEG,.frame_size = FRAMESIZE_VGA,.jpeg_quality = 12,.fb_count = 1,
};
void wifi_init()
{WiFi.mode(WIFI_STA);WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度WiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED){delay(500);Serial.print(".");}Serial.println("WiFi Connected!");Serial.print("IP Address:");Serial.println(WiFi.localIP());
}
esp_err_t camera_init() {//initialize the cameraesp_err_t err = esp_camera_init(&camera_config);if (err != ESP_OK) {Serial.println("Camera Init Failed");return err;}sensor_t * s = esp_camera_sensor_get();//initial sensors are flipped vertically and colors are a bit saturatedif (s->id.PID == OV2640_PID) {//        s->set_vflip(s, 1);//flip it back//        s->set_brightness(s, 1);//up the blightness just a bit//        s->set_contrast(s, 1);}Serial.println("Camera Init OK!");return ESP_OK;
}void setup()
{Serial.begin(115200);wifi_init();camera_init();
}void loop()
{Serial.println("Try To Connect TCP Server!");if (client.connect(serverIP, serverPort)) //尝试访问目标地址{Serial.println("Connect Tcp Server Success!");client.println("Frame Begin");  //46 72 61 6D 65 20 42 65 67 69 6E // 0D 0A 代表换行  //向服务器发送数据while (1){       camera_fb_t * fb = esp_camera_fb_get();uint8_t * temp = fb->buf; //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复if (!fb){Serial.println( "Camera Capture Failed");}else{ //先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送 //完毕后发送结束标志 Frame Over 表示一张图片发送完毕 client.print("Frame Begin"); //一张图片的起始标志// 将图片数据分段发送int leng = fb->len;int timess = leng/maxcache;int extra = leng%maxcache;for(int j = 0;j< timess;j++){client.write(fb->buf, maxcache); for(int i =0;i< maxcache;i++){fb->buf++;}}client.write(fb->buf, extra);client.print("Frame Over");      // 一张图片的结束标志Serial.print("This Frame Length:");Serial.print(fb->len);Serial.println(".Succes To Send Image For TCP!");//return the frame buffer back to the driver for reusefb->buf = temp; //将当时保存的指针重新返还esp_camera_fb_return(fb);  //这一步在发送完毕后要执行,具体作用还未可知。        }delay(30);//短暂延时 增加数据传输可靠性}/*while (client.connected() || client.available()) //如果已连接或有收到的未读取的数据{if (client.available()) //如果有数据可读取{String line = client.readStringUntil('\n'); //读取数据到换行符Serial.print("ReceiveData:");Serial.println(line);client.print("--From ESP32--:Hello Server!");    }}Serial.println("close connect!");client.stop(); //关闭客户端*/}else{Serial.println("Connect To Tcp Server Failed!After 10 Seconds Try Again!");client.stop(); //关闭客户端}delay(10000);
}

客户端代码, Python编写:

import socket
import threading
import time
import numpy as np
import cv2begin_data = b'Frame Begin'
end_data = b'Frame Over'# 接收数据
# ESP32发送一张照片的流程
# 先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送
# 完毕后发送结束标志 Frame Over 表示一张图片发送完毕
# 1430 来自ESP32cam发送的一个包大小为1430 接收到数据 data格式为b''
def handle_sock(sock, addr):temp_data = b''t1 = int(round(time.time() * 1000))while True:data = sock.recv(1430)# 如果这一帧数据包的开头是 b'Frame Begin' 则是一张图片的开始if data[0:len(begin_data)] == begin_data:# 将这一帧数据包的开始标志信息(b'Frame Begin')清除   因为他不属于图片数据data = data[len(begin_data):len(data)]# 判断这一帧数据流是不是最后一个帧 最后一针数据的结尾时b'Frame Over'while data[-len(end_data):] != end_data:temp_data = temp_data + data  # 不是结束的包 将数据添加进temp_datadata = sock.recv(1430)  # 继续接受数据 直到接受的数据包包含b'Frame Over' 表示是这张图片的最后一针# 判断为最后一个包 将数据去除 结束标志信息 b'Frame Over'temp_data = temp_data + data[0:(len(data) - len(end_data))]  # 将多余的(\r\nFrame Over)去掉 其他放入temp_data# 显示图片receive_data = np.frombuffer(temp_data, dtype='uint8')  # 将获取到的字符流数据转换成1维数组r_img = cv2.imdecode(receive_data, cv2.IMREAD_COLOR)  # 将数组解码成图像r_img = r_img.reshape(480, 640, 3)t2 = int(round(time.time() * 1000))fps = 1000 // (t2 - t1)cv2.putText(r_img, "FPS" + str(fps), (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)cv2.imshow('server_frame', r_img)if cv2.waitKey(1) & 0xFF == ord('q'):breakt1 = t2# print("接收到的数据包大小:" + str(len(temp_data)))  # 显示该张照片数据大小temp_data = b''  # 清空数据 便于下一章照片使用server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(("xxxx", xxxx))
server.send('monitor'.encode("utf-8"))
# server.bind(('0.0.0.0', 9090))
# server.listen(5)
# CONNECTION_LIST = []# 主线程循环接收客户端连接
handle_sock(server, '')  # mac电脑直接在主线程运行
# while True:# sock, addr = server.accept()# CONNECTION_LIST.append(sock)# print('Connect--{}'.format(addr))# 连接成功后开一个线程用于处理客户端# handle_sock(server, '')  # mac电脑直接在主线程运行# client_thread = threading.Thread(target=handle_sock, args=(sock, addr))# client_thread.start()

这里做一下特别说明, 在python代码的最后主线程那里, 由于我是运行在Mac电脑中, 所以不能开新的线程, 否则会报错, 如果你是运行在Windows电脑下, 请把注释打开, 创建新线程来运行.

下面是nodejs写的服务器端代码, 用于视频数据流的透传转发:

// 创建tcp连接服务
const net = require('net')
const { size } = require('underscore')
const HOST = 'xxxx'
const PORT = xxxx// 创建udp
// const dgram = require("dgram")
// const server = dgram.createSocket("udp4")// 统计连接客户端的个数
var count = 0// 创建slave_server服务
const slave_server = new net.createServer()
// slave_server.setEncoding = 'UTF-8'// 保存监视器的socket
var s// 获得一个连接,该链接自动关联scoket对象
var tcp_sock = null
slave_server.on('connection', sock => {tcp_sock = socksock.name = ++countconsole.log(`当前连接客户端数:${count}`)// 接收client发来的信息  sock.on('data', data => {// console.log(`客户端${sock.name}发来一个信息:${data}`)// 判断是否为监视器发来的链接if (data == 'monitor') {// 则把当前的socket保存起来s = sock} else {s.write(data)}})// 为socket添加error事件处理函数sock.on('error', error => { //监听客户端异常console.log('error' + error)sock.end()})// 服务器关闭时触发,如果存在连接,这个事件不会被触发,直到所有的连接关闭sock.on('close', () => {console.log(`客户端${sock.name}下线了`)count -= 1})
})// listen函数开始监听指定端口
slave_server.listen(PORT, () => {console.log(`服务器已启动,运行在:http://xxxx:xxxx`)
})

再次特别说明一下, 先启动服务器端代码, 然后在电脑端运行python端代码, 最后再烧录esp32-cam的代码, 第一次运行时电脑客户端可能报错直接就退出了, 没关系, 重新运行一下电脑客户端的python代码, 就能看到视频图像了. 我在服务器端的逻辑是接收到'monitor'字符串时认为是客户端监视器, 把对应的socket保存一下, 然后当接收到下一个socket连接时, 则默认认为是esp32-cam, 将之前保存的socket取出来, 把视频流数据转发出去, 此种方式只能有一个esp32-cam和一个客户端监视器, 有需要的小伙伴可以自行修改代码.

ESP32-CAM视频传输至公网服务器并转发视频数据流相关推荐

  1. 2021-12-04 micropython esp32 cam 照相并上传服务器, 参考贴 ,二进制字符串MQTT传输和转换,获取字典键的技巧, 4G MQTT 串口分段传送大文件

    参考链接1 参考链接2 参考链接3 参考链接4 写在前面,联网设备和照相模组都是耗能大户,没有良好的供电其他都免谈,在保证供电充足的情况下进行调试~ 下载的时候要把特定引脚拉低,参照上面的参考连接. ...

  2. JavaCV音视频开发宝典:rtp点到点音视频传输TS流(一对一音视频直播)

    <JavaCV音视频开发宝典>专栏目录导航 <JavaCV音视频开发宝典>专栏介绍和目录 ​ 前言 在开始之前,我们需要了解一点rtp协议相关的知识. 当然了,相信很多小伙伴应 ...

  3. 视频监控系统app服务器如何设置,视频监控系统如何配置摄像机自动布防、防偷防盗功能...

    长达八天的国庆.中秋长假到来了,相信不少朋友有外出旅游.回乡探亲的计划.家里长时间没有人,会不会有小偷光顾呢?门口有没有陌生人?无人照料的小宠物们是不是尽情在家闯祸?此时我们摄像头的贴心功能就可以派上 ...

  4. ios微信本地视频上传到服务器,ios本地视频wx.uploadFile上传

    //上传视频 uploadVideo:function(){ let _this = this; let list = ['camera', 'album']; wx.showActionSheet( ...

  5. 视频传输面临的挑战和解决之道

    音视频行业的发展,用户对音视频画质的清晰度.播放的流畅度.互动的低延迟.突破终端限制等的要求越来越高.这些需求从客观上对视频的传输提出更高的挑战,而目前不同业务的视频传输方式各有不同,如何基于视频传输 ...

  6. 华为云专家私房课:视频传输技术选型的三大法宝

    摘要:华为云视频架构师黄挺,将从视频传输现状入手,剖析不同业务选择不同视频传输方式的背后逻辑,分享华为云新媒体网络价值主张. 音视频行业的发展,用户对音视频画质的清晰度.播放的流畅度.互动的低延迟.突 ...

  7. 访问服务器上视频的文件,服务器上的视频文件

    服务器上的视频文件 内容精选 换一换 抽帧截图功能根据时间间隔采样抽帧,均匀的截取对应视频的多帧图像,然后保存为图片文件.您可以通过API或者调用转码SDK对视频进行抽帧截图.媒体处理服务提供灵活的抽 ...

  8. OnRL: 基于在线强化学习的移动视频传输优化

    从2019年开始,淘系技术部内容社交互动团队和北京邮电大学周安福教授一起着手研究更好的基于机器学习的智能拥塞控制算法.在实验室环境完成原型验证后在淘宝直播的生产环境做实际效果对比,从实际数据来看效果明 ...

  9. 强化学习驱动的低延迟视频传输

    随着视频会议.视频直播的流行以及未来AR/VR业务的发展,低延迟视频传输服务被广泛使用,但视频质量(QoE)还不能满足用户要求.那么近年来新兴的AI神经网络是否能为视频传输带来智能化的优化?今天Liv ...

最新文章

  1. 边缘计算不再“边缘”
  2. 禁用/启用按钮和链接的最简单方法是什么(jQuery + Bootstrap)
  3. 07-MyBatis 核心配置文件
  4. 前端学习(3325):Mdn说闭包
  5. 什么是python之禅_【Python面试】你了解什么是 Python 之禅么?
  6. HR怎么从面试中了解程序员的真实水平?需要面试的程序员,注意了!
  7. mysql crud_如何使用Laravel和MySQL构建您的第一个CRUD应用
  8. python实现神经网络_3. Python矢量化实现神经网络
  9. [转]Oracle字符串拼接的方法
  10. 2022新和平精英画质助手iApp源码+附成品/可用的
  11. css向右箭头形状按钮,详解Bootstrap的纯CSS3箭头按钮样式
  12. 计算机睡眠和休眠哪个好,休眠和睡眠哪个好?电脑休眠和睡眠有什么区别?
  13. FineReport10 决策报表常用javascript脚本
  14. 联通NFV规划路线图:数据中心为核心 实现网元虚拟化
  15. 离线安装金蝶Apusic中间件
  16. 计算机控制 采样电路,计算机模拟量的采样与处理
  17. JavaScript事件触发
  18. 综合治理GIS方案(综治)
  19. 2021.3.1版本idea搭建springMVC项目时出现的问题及解决
  20. java mysql 1366_MySql中的error-code='1366', sqlstate='HY000'错误

热门文章

  1. java fit 16s,16s分析之差异OTU 挑选(edgeR)
  2. 翁恺c语言视频作业题,翁恺入门C语言第2周编程练习
  3. 网站服务器过载,服务器过载保护
  4. c语言 | 求1000-2000年之间的闰年
  5. for循环2000年到3000年的闰年
  6. 树莓派3初始化安装(Raspberry Pi III)
  7. 压缩文件压缩率是什么意思
  8. 新疆计算机一级考试excel公式,2020年XX专业技术人员继续教育公需课《Excel快速统计》试题及答案...
  9. SpringBoot + Element UI 楠橘星后台管理系统一键打包部署教程文档
  10. 什么是GB18030编码?