安卓App与ESP32Cam的视频传输

  1. 实现结果

  2. app可以控制Esp32Cam的摄像头开关和闪光灯的开关
  3. Esp32Cam代码
     
    #include 
    #include 
    #include "esp_camera.h"
    #include #define maxcache 1024  //图像数据包的大小const char* ssid = "****";
    const char* password = "*******";const int LED = 4;//闪光灯
    const int ZHESHI_LED = 33; //指示灯 
    bool cam_state = true;  //是否开启摄像头传输
    const int port = 8080;
    String  frame_begin = "FrameBegin"; //图像传输包头
    String  frame_over = "FrameOverr";  //图像传输包尾
    String  msg_begin = "Esp32Msg";  //消息传输头
    //创建服务器端
    WiFiServer server;
    //创建客户端
    WiFiClient 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 = 31,   //图像质量   0-63  数字越小质量越高.fb_count = 1,
    };
    //初始化摄像头
    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;
    }bool wifi_init(const char* ssid,const char* password ){WiFi.mode(WIFI_STA);WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度#ifdef staticIPWiFi.config(staticIP, gateway, subnet);#endifWiFi.begin(ssid, password);uint8_t i = 0;Serial.println();while (WiFi.status() != WL_CONNECTED && i++ < 20) {delay(500);Serial.print(".");}if (i == 21) {Serial.println();Serial.print("Could not connect to"); Serial.println(ssid);digitalWrite(ZHESHI_LED,HIGH);  //网络连接失败 熄灭指示灯return false;}Serial.print("Connecting to wifi "); Serial.print(ssid);Serial.println(" success!"); digitalWrite(ZHESHI_LED,LOW);  //网络连接成功 点亮指示灯return true;
    }void TCPServerInit(){//启动serverserver.begin(port);//关闭小包合并包功能,不会延时发送数据server.setNoDelay(true);Serial.print("Ready! TCP Server");Serial.print(WiFi.localIP());Serial.println(":8080 Running!");
    }
    void cssp(){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(20);//短暂延时 增加数据传输可靠性        
    }
    void TCPServerMonitor(){
    if (server.hasClient()) {if ( client && client.connected()) {WiFiClient serverClient = server.available();serverClient.stop();Serial.println("Connection rejected!");}else{//分配最新的clientclient = server.available();client.println(msg_begin +  "Client is Connect!");Serial.println("Client is Connect!");}
    }//检测client发过来的数据
    if (client && client.connected()) {if (client.available()) {String line = client.readStringUntil('\n'); //读取数据到换行符if (line == "CamOFF"){cam_state = false;client.println(msg_begin +  "Camera OFF!");}if (line == "CamON"){cam_state = true;client.println(msg_begin +  "Camera ON!");}if (line == "LedOFF"){digitalWrite(LED, LOW);client.println(msg_begin +  "Led OFF!");}if (line == "LedON"){digitalWrite(LED, HIGH);client.println(msg_begin +  "Led ON!");}Serial.println(line);}
    }// 视频传输
    if(cam_state)
    {if (client && client.connected()) {cssp();}
    }
    }void setup() {Serial.begin(115200);pinMode(ZHESHI_LED, OUTPUT);digitalWrite(ZHESHI_LED, HIGH);pinMode(LED, OUTPUT);digitalWrite(LED, LOW);wifi_init(ssid,password);camera_init();TCPServerInit();
    }void loop() {TCPServerMonitor();
    }
    

  4. 安卓app代码
     
    package com.example.tcpclient_eap32cam_1025;import androidx.appcompat.app.AppCompatActivity;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.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.Date;public class MainActivity extends AppCompatActivity {EditText host_editText,port_editText,send_data;TextView rec_data;Button connect_button,send;ImageView show_cam;Socket socket;InputStream inputStream;OutputStream outputStream;byte[] RevBuff = new byte[1024];  //定义接收数据流的包的大小MyHandler myHandler;byte[] temp = new byte[0];  //存放一帧图像的数据int headFlag = 0;    // 0 数据流不是图像数据   1 数据流是图像数据Bitmap bitmap = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);host_editText = findViewById(R.id.host_editText); //服务器地址port_editText = findViewById(R.id.port_editText);//服务器端口connect_button= findViewById(R.id.connect_button);//连接服务器按钮rec_data = findViewById(R.id.rec_data); //存放接收到的非图像数据send = findViewById(R.id.send);//发送数据按钮send_data = findViewById(R.id.send_data);//发送数据文本框connect_button.setText("连接"); //设置连接按钮名称为连接 如果已连接上显示断开show_cam = findViewById(R.id.show_cam); //存放图像数据myHandler = new MyHandler();
    //        连接服务器操作connect_button.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {if(connect_button.getText() == "连接"){new Thread(new Runnable() {@Overridepublic void run() {Message msg = myHandler.obtainMessage();try {//如果 host_editText  port_editText为空的话 点击连接 会退出程序socket = new Socket((host_editText.getText()).toString(),Integer.valueOf(port_editText.getText().toString()));//socket = new Socket("192.168.0.3",8080);if(socket.isConnected()){msg.what = 0;//显示连接服务器成功信息inputStream = socket.getInputStream();outputStream = socket.getOutputStream();Recv();//接收数据}else{msg.what = 1;//显示连接服务器失败信息}} catch (IOException e) {e.printStackTrace();msg.what = 1;//显示连接服务器失败信息}myHandler.sendMessage(msg);}}).start();}else{
    //                    关闭socket连接try { socket.close(); } catch (IOException e) { e.printStackTrace(); }try { inputStream.close(); }catch (IOException e) { e.printStackTrace(); }try { outputStream.close(); }catch (IOException e) { e.printStackTrace(); }connect_button.setText("连接");}}});
    //        发送数据send.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {new Thread(new Runnable() {@Overridepublic void run() {try {
    //                            发送数据outputStream.write(send_data.getText().toString().getBytes());} catch (IOException e) {
    //                            如果发送数据失败 显示连接服务器失败信息e.printStackTrace();Message msg = myHandler.obtainMessage();msg.what = 1;myHandler.sendMessage(msg);}}}).start();}});}//    接收数据方法public void Recv(){new Thread(new Runnable() {@Overridepublic void run() {while(socket != null && socket.isConnected()){try {int Len = inputStream.read(RevBuff);if(Len != -1){
    //                          图像数据包的头  FrameBeginboolean 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 ;
    //                            图像数据包的尾  FrameOverrboolean 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;
    //                            判断接收的包是不是图片的开头数据 是的话s说明下面的数据属于图片数据 将headFlag置1if(headFlag == 0 && begin_cam_flag){headFlag = 1;}else if(end_cam_flag){  //判断包是不是图像的结束包 是的话 将数据传给 myHandler  3 同时将headFlag置0Message msg = myHandler.obtainMessage();msg.what = 3;myHandler.sendMessage(msg);headFlag = 0;}else if(headFlag == 1){ //如果 headFlag == 1 说明包是图像数据  将数据发给byteMerger方法 合并一帧图像temp = byteMerger(temp,RevBuff);}
    //                            定义包头 Esp32Msg  判断包头 在向myHandler  2 发送数据    eadFlag == 0 && !end_cam_flag没用 会展示图像的数据boolean begin_msg_begin = RevBuff[0] == 69 && RevBuff[1] == 115 && RevBuff[2] == 112 && RevBuff[3] == 51 && RevBuff[4] == 50&& RevBuff[5] == 77 && RevBuff[6] == 115 && RevBuff[7] == 103 ;if(begin_msg_begin){Message msg = myHandler.obtainMessage();msg.what = 2;msg.arg1 = Len;msg.obj = RevBuff;myHandler.sendMessage(msg);}}else{
    //                            如果Len = -1 说明接受异常  显示连接服务器失败信息  跳出循环Message msg = myHandler.obtainMessage();msg.what = 1;myHandler.sendMessage(msg);break;}} catch (IOException e) {
    //                        如果接受数据inputStream.read(RevBuff)语句执行失败 显示连接服务器失败信息  跳出循环e.printStackTrace();Message msg = myHandler.obtainMessage();msg.what = 1;myHandler.sendMessage(msg);break;}}}}).start();}//    合并一帧图像数据  a 全局变量 temp   b  接受的一个数据包 RevBuffpublic byte[] byteMerger(byte[] a,byte[] b){int i = a.length + b.length;byte[] t = new byte[i]; //定义一个长度为 全局变量temp  和 数据包RevBuff 一起大小的字节数组 tSystem.arraycopy(a,0,t,0,a.length);  //先将 temp(先传过来的数据包)放进  tSystem.arraycopy(b,0,t,a.length,b.length);//然后将后进来的这各数据包放进treturn t; //返回t给全局变量 temp}//处理一些不能在线程里面执行的信息class MyHandler extends Handler{public void handleMessage(Message msg){super.handleMessage(msg);switch (msg.what){case 0:
    //                    连接服务器成功信息Toast.makeText(MainActivity.this,"连接服务器成功!",Toast.LENGTH_SHORT).show();connect_button.setText("断开");break;case 1:
    //                    连接服务器失败信息Toast.makeText(MainActivity.this,"连接服务器失败!",Toast.LENGTH_SHORT).show();break;case 2:
    //                    处理接收到的非图像数据byte[] Buffer = new byte[msg.arg1];System.arraycopy((byte[])msg.obj,0,Buffer,0,msg.arg1);SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");Date date = new Date(System.currentTimeMillis());String content = (new String(Buffer)) + "----"  + formatter.format(date) + "\n";rec_data.append(content);break;case 3:
    //                    处理接受到的图像数据 并展示bitmap = BitmapFactory.decodeByteArray(temp, 0,temp.length);show_cam.setImageBitmap(bitmap);//这句就能显示图片(bitmap数据没问题的情况下) 存在图像闪烁情况 待解决temp = new byte[0];  //一帧图像显示结束  将 temp清零break;default: break;}}}//    销毁窗体 释放资源protected void onDestroy() {super.onDestroy();if(inputStream != null){try {inputStream.close();}catch(IOException e) {e.printStackTrace();}}if(outputStream != null){try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}if(socket != null){try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
    }
    
    

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部