esp32cam
/*
下载程序 按住接口板上的IO0 在程序上传的时候 按一下 开发板上的rst按钮 待程序开始上传 在松开 IO0
brownout detector was triggered报错 触发了断电探测器,估计是供电环境本来就不稳定
屏蔽
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable detector
//Your code
}
*/
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <WiFi.h> //wifi功能需要的库
#include <Arduino.h>
#include "esp_camera.h"
#include <vector>
#include <WiFiUdp.h> //引用以使用UDP
WiFiUDP Udp; //声明UDP对象
const char* wifi_SSID = "esp32"; //存储AP的名称信息 生成一个wifi 网络 方便客户端连接
const char* wifi_Password = "12345678"; //存储AP的密码信息
uint16_t udp_port = 1122; //存储需要监听的端口号 eps32cam接收数据 和 往其他客户端 发送数据的端口
#define maxcache 1430 //图片数据分帧发送
bool is_tran_camera = false; //是否传输视频数据
IPAddress ip_address; //客户端的ip
#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 22
static 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,
};
//摄像头初始化
esp_err_t camera_init() {
//initialize the camera
esp_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 saturated
if (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);
}
return ESP_OK;
}
//接收数据的处理函数
void recv_data() {
int packetSize = Udp.parsePacket(); //获取当前队首数据包长度
if (packetSize) //如果有数据可用
{
char buf[packetSize];
Udp.read(buf, packetSize); //读取当前包数据
ip_address = Udp.remoteIP();
String tmp = String(buf);
//打开视频数据传输
if(tmp.substring(0,5) == "camon"){
is_tran_camera = true;
Udp.beginPacket(ip_address, udp_port);
Udp.print("Camera is ON!");
Udp.endPacket();
}else if(tmp.substring(0,6) == "camoff"){ // 关闭视频数据传输
is_tran_camera = false;
Udp.beginPacket(ip_address, udp_port);
Udp.print("Camera is OFF!");
Udp.endPacket();
}else{
Udp.beginPacket(ip_address, udp_port);
Udp.print("RecvData:" + tmp);
Udp.endPacket();
}
}
}
// 传输视频数据
void cssp() {
camera_fb_t* fb = esp_camera_fb_get();
uint8_t* temp = fb->buf; //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
if (!fb) {
// Serial.print("Camera Capture Failed!");
Udp.beginPacket(ip_address, udp_port);
Udp.print("Camera Capture Failed!");
Udp.endPacket();
} else {
Udp.beginPacket(ip_address, udp_port);
Udp.print("FrameBegin"); //视频数据的标志头
Udp.endPacket();
// 将图片数据分段发送
int leng = fb->len;
int timess = leng / maxcache;
int extra = leng % maxcache;
for (int j = 0; j < timess; j++) {
Udp.beginPacket(ip_address, udp_port);
Udp.write(fb->buf, maxcache);
Udp.endPacket();
for (int i = 0; i < maxcache; i++) {
fb->buf++;
}
}
// 发送剩余数据
Udp.beginPacket(ip_address, udp_port);
Udp.write(fb->buf, extra);
Udp.endPacket();
Udp.beginPacket(ip_address, udp_port);
Udp.print("FrameOverr"); //视频数据的标志尾
Udp.endPacket();
// Serial.print("This Frame Length:");
// Serial.print(fb->len);
// Serial.println(". \t Succes To Send Image For UDP!");
//return the frame buffer back to the driver for reuse
fb->buf = temp; //将当时保存的指针重新返还
esp_camera_fb_return(fb); //这一步在发送完毕后要执行,具体作用还未可知。
}
delay(20); //不加延时会导致数据发送混乱 稍微延时增加数据传输可靠性
}
// udp服务初始化
void udp_init() {
WiFi.softAP(wifi_SSID, wifi_Password); //打开ESP32热点
Udp.begin(udp_port); //启动UDP监听这个端口
}
void setup() {
delay(5000);
Serial.begin(115200); //开启串口,波特率为115200
//在创建udp服务以后 使用串口输出数据会报错 开发板一直重启
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable detector 屏蔽 brownout detector was triggered报错 防止一直重启
udp_init();
camera_init();
Serial.print(WiFi.softAPIP()); //串口输出模块IP地址
Serial.print(":"); //串口输出模块IP地址
Serial.println(udp_port);
Serial.println("Sys Is Running!");
}
void loop() {
recv_data();
if (is_tran_camera) {
cssp();
}
}
Android
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/local_ip_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="" />
<TextView
android:id="@+id/local_port_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="" />
<Button
android:id="@+id/startudp_btn_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="开启UDP服务" />
<ImageView
android:id="@+id/show_cam_kj"
android:layout_width="match_parent"
android:layout_height="220dp"
android:background="#333"
android:scaleType="center"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/recv_data_kj"
android:layout_width="match_parent"
android:layout_height="100dp"
android:text="" />
<TextView
android:id="@+id/remote_ip_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="192.168.4.1" />
<TextView
android:id="@+id/remote_port_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="1122" />
<EditText
android:id="@+id/msg_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="" />
<Button
android:id="@+id/send_btn_kj"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="发送" />
</LinearLayout>
package com.example.esp32cam_udp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {
private TextView local_ip_kj,local_port_kj,remote_ip_kj,remote_port_kj,recv_data_kj;
private Button startudp_btn_kj,send_btn_kj;
private ImageView show_cam_kj;
private EditText msg_kj;
String local_ip = "";
int port = 1122;
private DatagramSocket socket; //接收数据用的socket
int data_len = 1430;//与esp32cam约定的数据传输的长度
int headFlag = 0;// 0 数据流不是图像数据 1 数据流是图像数据
byte[] RevBuff = new byte[data_len];//定义接收数据流的包的大小
byte[] temp = new byte[0]; //存放一帧图像的数据
Bitmap bitmap = null; //展示图片
MyHandler myHandler; //处理信息
String recv_data = ""; //保存接收到的非图像数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myHandler = new MyHandler();
show_cam_kj = findViewById(R.id.show_cam_kj);
local_ip_kj = findViewById(R.id.local_ip_kj);
local_port_kj = findViewById(R.id.local_port_kj);
remote_ip_kj = findViewById(R.id.remote_ip_kj);
remote_port_kj = findViewById(R.id.remote_port_kj);
msg_kj = findViewById(R.id.msg_kj);
startudp_btn_kj = findViewById(R.id.startudp_btn_kj);
send_btn_kj = findViewById(R.id.send_btn_kj);
recv_data_kj = findViewById(R.id.recv_data_kj);
//获取本机ip地址
local_ip = IPUtils.getIpAddress(this);
Message msg = myHandler.obtainMessage();
msg.what = 0;
msg.obj = local_ip;
myHandler.sendMessage(msg);
//Log.e("IP",local_ip);
// 开启udp服务
startudp_btn_kj.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//生成接受数据使用的socket
try {
InetAddress serverAdder = InetAddress.getByName(local_ip); //本手机的ip地址
socket = new DatagramSocket(port,serverAdder);
Message msg = myHandler.obtainMessage();
msg.what = 1;
msg.obj = "UDP服务开启成功!";
myHandler.sendMessage(msg);
// 开启接收数据的线程
new Thread(new RecvData()).start();
} catch (SocketException | UnknownHostException e) {
e.printStackTrace();
}
}
});
// 发送消息
send_btn_kj.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new SendData()).start();
}
});
}
public class SendData implements Runnable{
@Override
public void run() {
String server_ip = remote_ip_kj.getText().toString();
int server_port = Integer.parseInt(remote_port_kj.getText().toString());
InetAddress server_serverAdder = null;
try {
server_serverAdder = InetAddress.getByName(server_ip);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
DatagramSocket send_socket = null;
try {
send_socket = new DatagramSocket();
} catch (SocketException e) {
throw new RuntimeException(e);
}
String tmp = msg_kj.getText().toString();
byte[] buf = tmp.getBytes();
DatagramPacket send_packet = new DatagramPacket(buf, buf.length, server_serverAdder, server_port);
try {
send_socket.send(send_packet);
} catch (IOException e) {
throw new RuntimeException(e);
}
send_socket.close();
}
}
// 接收数据的线程
public class RecvData implements Runnable {
public void run() {
// 接收UDP广播,有的手机不支持
while (true) {
byte[] recbuf = new byte[data_len];
DatagramPacket recpacket = new DatagramPacket(recbuf,
recbuf.length);
try {
socket.receive(recpacket);
RevBuff = recpacket.getData();
// 图像数据包的头 FrameBegin
boolean begin_cam_flag = RevBuff[0] == 70 && RevBuff[1] == 114 && RevBuff[2] == 97 && RevBuff[3] == 109 && RevBuff[4] == 101
&& RevBuff[5] == 66 && RevBuff[6] == 101 && RevBuff[7] == 103 && RevBuff[8] == 105 && RevBuff[9] == 110;
// 图像数据包的尾 FrameOverr
boolean end_cam_flag = RevBuff[0] == 70 && RevBuff[1] == 114 && RevBuff[2] == 97 && RevBuff[3] == 109 && RevBuff[4] == 101
&& RevBuff[5] == 79 && RevBuff[6] == 118 && RevBuff[7] == 101 && RevBuff[8] == 114 && RevBuff[9] == 114;
if (headFlag == 0 && begin_cam_flag) {
headFlag = 1;
} else if (end_cam_flag) { //判断包是不是图像的结束包 是的话 将数据传给 myHandler 3 同时将headFlag置0
Message msg = myHandler.obtainMessage();
msg.what = 3;
myHandler.sendMessage(msg);
headFlag = 0;
} else if (headFlag == 1) { //如果 headFlag == 1 说明包是图像数据 将数据发给byteMerger方法 合并一帧图像
temp = byteMerger(temp, RevBuff);
}else{
//接收到的非图像数据
Message msg = myHandler.obtainMessage();
msg.what = 2;
msg.obj = new String(RevBuff); //字符串长度
myHandler.sendMessage(msg);
// Log.v("Message:",new String(RevBuff));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//处理一些不能在线程里面执行的信息
class MyHandler extends Handler {
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what){
case 0:
// 获取ip c成功
local_ip_kj.setText(local_ip);
local_port_kj.setText(String.valueOf(port));
break;
case 1:
// UDP服务开启成功
Toast.makeText((Context) MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show();
break;
case 2:
// 处理接收到的非图像数据
// Toast.makeText((Context) MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show();
String tmp = recv_data + msg.obj.toString() + "\n";
recv_data = tmp;
recv_data_kj.setText(tmp);
break;
case 3:
try {
//处理接受到的图像数据 并展示
bitmap = BitmapFactory.decodeByteArray(temp, 0,temp.length);
show_cam_kj.setImageBitmap(bitmap);//这句就能显示图片(bitmap数据没问题的情况下) 存在图像闪烁情况 待解决
temp = new byte[0]; //一帧图像显示结束 将 temp清零
}catch (Exception e){
Log.i("Error","Error image data!");
}
break;
default: break;
}
}
}
// 合并一帧图像数据 a 全局变量 temp b 接受的一个数据包 RevBuff
public byte[] byteMerger(byte[] a,byte[] b){
int i = a.length + b.length;
byte[] t = new byte[i]; //定义一个长度为 全局变量temp 和 数据包RevBuff 一起大小的字节数组 t
System.arraycopy(a,0,t,0,a.length); //先将 temp(先传过来的数据包)放进 t
System.arraycopy(b,0,t,a.length,b.length);//然后将后进来的这各数据包放进t
return t; //返回t给全局变量 temp
}
@Override
protected void onDestroy() {
super.onDestroy();
socket.close();
}
}
package com.example.esp32cam_udp;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* =======================================================
* 版权:Copyright LiYing 2015-2016. All rights reserved.
* 作者:liying - liruoer2008@yeah.net
* 日期:2016/12/19 19:43
* 版本:1.0
* 描述:IP地址工具类
* 备注:返回本机IP地址 文件名称 IPUtils.java 与MainActivity并列位置
* =======================================================
*/
public class IPUtils {
/**
* 获取本机IPv4地址
*
* @param context
* @return 本机IPv4地址;null:无网络连接
*/
public static String getIpAddress(Context context) {
// 获取WiFi服务
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
// 判断WiFi是否开启
if (wifiManager.isWifiEnabled()) {
// 已经开启了WiFi
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipAddress = wifiInfo.getIpAddress();
String ip = intToIp(ipAddress);
return ip;
} else {
// 未开启WiFi
return getIpAddress();
}
}
private static String intToIp(int ipAddress) {
return (ipAddress & 0xFF) + "." +
((ipAddress >> 8) & 0xFF) + "." +
((ipAddress >> 16) & 0xFF) + "." +
(ipAddress >> 24 & 0xFF);
}
/**
* 获取本机IPv4地址
*
* @return 本机IPv4地址;null:无网络连接
*/
private static String getIpAddress() {
try {
NetworkInterface networkInterface;
InetAddress inetAddress;
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
networkInterface = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = networkInterface.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
return inetAddress.getHostAddress();
}
}
}
return null;
} catch (SocketException ex) {
ex.printStackTrace();
return null;
}
}
}
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
</manifest>