[ESP32]I2S和Camera的应用--网络摄像头

一、前言

        天气越来越热,于是我把我的狗子牵到屋子里面避暑,为了防止狗子在一个狗的屋子里乱拆,灵机一动便有了这篇文章。(文章末尾附完整工程)

二、硬件选型

发送端采用ESP32-S3,摄像头使用ov2640

功放芯片选择MAX98357A+8Ω2W的喇叭

接收端选择了一个普通的ESP32 开发板,带了一个ILI9431的屏幕

麦克风选择INMP441

三、硬件连接 

ESP32-S3硬件连接

CAM_PIN_PWDN -

1

CAM_PIN_RESET

45

CAM_PIN_XCLK

39

CAM_PIN_SIOD

21

CAM_PIN_SIOC

46

CAM_PIN_D7

40

CAM_PIN_D6

38

CAM_PIN_D5

37

CAM_PIN_D4

35

CAM_PIN_D3

33

CAM_PIN_D2

48

CAM_PIN_D1

47

CAM_PIN_D0

34

CAM_PIN_VSYNC

42

CAM_PIN_HREF

41
CAM_PIN_PCLK36

MAX98357_BCK 

18

MAX98357_LRC

16
MAX98357_DIN17
MAX98357_SD3.3V

接收端的ESP32硬件连接

LCD_MOSI13
LCD_SCK14
LCD_CS15
LCD_DC2
LCD_RST4
LCD_BLK21

INMP441_WS

21

INMP441_SD

22
INMP441_SCK27
INMP441_L/RGND

四、功能实现

接收端

接收端使用WiFi STA模式,初始化为TCP Client用于连接发送端发出的热点。(具体代码:略)

接收端的任务主要是:

        1.接受WIFI上的JPEG图片并解码显示

        2.采集iis音频数据并发送

首先创建两个任务,一个用于iis读取音频,另一个任务用于将音频数据通过WIFI发送出去。两个任务之间共享一个链队用于存储数据,链队用信号量互斥访问。

    //初始化链队和互斥信号量 
    LinkQueue Queue_Wifi_Transmit;
    SemaphoreHandle_t Mutex_Wifi_Transmit;	
    Mutex_Wifi_Transmit  = xSemaphoreCreateMutex();
    InitLinkQueue( &Queue_Wifi_Transmit);

采集音频的任务放在app_main()中执行

 while(1){
		if(xSemaphoreTake(Mutex_Wifi_Transmit,portMAX_DELAY) == pdTRUE){
			if(GetLength(Queue_Wifi_Transmit) < 20){
				memset(Audio_Data,0,1024);
				ret = i2s_read(I2S_NUM_0, (void *)Audio_Data, 1024, &bytes_read, 100);
				if(ret == ESP_OK){
					if(EnLinkQueue(&Queue_Wifi_Transmit,(unsigned char *)Audio_Data) == 1){

                    }
				}
			}
			xSemaphoreGive(Mutex_Wifi_Transmit);
		}
		vTaskDelay(10 / portTICK_RATE_MS);
    }

创建第二个任务用于发送音频数据 

xTaskCreate(Tcp_Transmit,"tcp_transmit_task",4096,NULL,2,&Tcp_Transmit_handle);  //创建WIFI发送任务
void Tcp_Transmit(void *pvParameters){
	err_t ret;
    ESP_LOGI(TAG,"TCP发送任务开始");
    for(;;){
		if(xSemaphoreTake(Mutex_Wifi_Transmit,portMAX_DELAY) == pdTRUE){
			if(GetLength(Queue_Wifi_Transmit) != 0){
				if(GetHead(Queue_Wifi_Transmit,(uint8_t *)Wifi_Send_Data) == 1){
					ret = send(connect_sock,Wifi_Send_Data,1024,0);
					if(ret == ESP_OK){
						if(DeLinkQueue(&Queue_Wifi_Transmit)==1){

                        }
					}
				}
			}
			xSemaphoreGive(Mutex_Wifi_Transmit);
		}
        vTaskDelay(10 / portTICK_RATE_MS);
    }
}

处理完音频数据之后,开始处理视频数据,如果传RGB图片的话原始数据太大了,所以让ov2640直接输出JPEG图片,这就涉及到一个JPEG解码。

使用Jpeglib库(移植jpeglib库参考百度)来解码。

因为我这个ESP32 的开发板没有外置PSRAM,而内置的RAM空间不够,所以只能每解码10行就显示一次。屏幕驱动函数使用的是lvgl的驱动(如果RAM空间够的话也可以用LVGL显示)

#define JPEG_Decode_Line 10
int jpegDecode(void)
{
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);
    jpeg_mem_src(&cinfo, (void *)jpeg_data, sizeof(jpeg_data));
    jpeg_read_header(&cinfo, TRUE);
    cinfo.out_color_space = JCS_RGB;
    jpeg_start_decompress(&cinfo);
    // printf("height%d\n",cinfo.output_height);
    // printf("width%d\n",cinfo.output_width);
    // printf("scanline%d\n",cinfo.output_scanline);
    // printf("width%d\n",cinfo.output_width);
	JSAMPARRAY out_buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
	out_buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * 320 * 3);
	int j =0;
	while (cinfo.output_scanline < cinfo.output_height)
	{
		jpeg_read_scanlines(&cinfo, out_buffer, 1);				//空间不够,边解析边显示
		memcpy(&data[j], out_buffer[0], cinfo.image_width * 3);
		j += cinfo.image_width * 3;
		if((cinfo.output_scanline+1)%JPEG_Decode_Line==0){
			int k = 0;
			j = 0;
			for(int i = 0;i<JPEG_Decode_Line*320*3;i+=3){
    			uint16_t rgb =(((data[i+0] >> 3) & 0x1F) << 11)//b
								|(((data[i+1] >> 2) & 0x3F) << 5)//g
								|(((data[i+2] >> 3) & 0x1F) << 0);//r
				data[k++] = (unsigned char)((rgb>>8)&0xff);
				data[k++] = (unsigned char)((rgb)&0xff);
			}
			lcd_flush(0,((cinfo.output_scanline+1)/JPEG_Decode_Line-1)*JPEG_Decode_Line,320-1,(cinfo.output_scanline+1)-1,(uint8_t *)data);
			//ESP_LOGW("解码之后","%d %d %d %d",0,((cinfo.output_scanline+1)/10-1)*10,320-1,(cinfo.output_scanline+1)-1);
			vTaskDelay(10 / portTICK_PERIOD_MS);
		}
	}
	free(out_buffer[0]);
	free(out_buffer);
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    return 0;
}

解码函数编写完成后,再创建一个任务用于接受WIFI上的JEPG图片数据

void Tcp_Receive(){
    ESP_LOGI(TAG,"TCP接收任务开始");
    int len;
    unsigned char rx_buffer[1024];
    while(1)
    {
		memset(rx_buffer, 0, sizeof(rx_buffer));    // 清空缓存
		len = recv(connect_sock, rx_buffer, 1024, 0);  // 阻塞读取接收数据
		if(len < 0)
		{
			ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
			break;
		}
		else if (len == 0)
		{
			ESP_LOGW(TAG, "Connection closed");
			break;
		}
		else
		{
			for(int i =0;i<len;i++){
				jpeg_data[jpg_point] = rx_buffer[i];
				jpg_point = (jpg_point + 1)%10240;
				if(jpg_point == 2 || jpeg_zt == 1){
					//ESP_LOGW(TAG,"%d %d",jpeg_data[0],jpeg_data[1]);
					if(jpeg_data[0] == 0xff && jpeg_data[1] == 0xd8){
						jpeg_zt = 1;
						if(jpeg_data[jpg_point - 2] == 0xff && jpeg_data[jpg_point - 1] == 0xd9){
							jpegDecode();
							for(int j=0;j<=jpg_point;j++){
								jpeg_data[j] = 0;
							}
							jpg_point = 0;
							jpeg_zt = 0;
						}
					}else{
						jpg_point = 0;
					}
				}
			}
		}
        vTaskDelay(10 / portTICK_RATE_MS);
    }
}

发送端

发送端使用WiFi AP模式,初始化为TCP Server用于监听接收端的连接。(具体代码:略)

发送端的任务主要是:

        1.采集ov2640的JPEG数据并发送

        2.接受WIFI上的音频数据并用IIS驱动MAX98357a播放

同样是先创建两个任务把音频数据解决掉,任务之间依然共享一个链队用于存储数据,链队用信号量互斥访问。

将接受到的数据存储在队列中

void Tcp_Receive(){
    ESP_LOGI(TAG,"TCP接收任务开始");
    int len;
    unsigned char rx_buffer[1024];
    while(1)
    {
        memset(rx_buffer, 0, sizeof(rx_buffer));    // 清空缓存
        len = recv(connect_sock, rx_buffer, 1024, 0);  // 阻塞读取接收数据
        if(len < 0)
        {
            ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
            break;
        }
        else if (len == 0)
        {
            ESP_LOGW(TAG, "Connection closed");
            break;
        }
        else
        {
            ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
            if(xSemaphoreTake(Mutex_wifi_RX,portMAX_DELAY)==pdTRUE){
                if(GetLength(Queue_wifi_RX)<20){
                    if(EnLinkQueue(&Queue_wifi_RX,rx_buffer)==1){

                    }
                }
                xSemaphoreGive( Mutex_wifi_RX );
            }
        }
        vTaskDelay(10 / portTICK_RATE_MS);
    }
}

在app_main()中将数据发送给MAX98357a

    while (1)
    {
        if(xSemaphoreTake(Mutex_wifi_RX,portMAX_DELAY) == pdTRUE){
            if(GetLength(Queue_wifi_RX) != 0){
                memset(Audio_Data,0,1024);
                //memset(Decode_Audio_Data,0,512);
                if(GetHead(Queue_wifi_RX,(uint8_t *)Audio_Data) == 1){
                    err_t ret = i2s_write(I2S_NUM_0, (void *)Audio_Data, 1024, &bytesOut, portMAX_DELAY);
                    if(ret == ESP_OK){
                        if(DeLinkQueue(&Queue_wifi_RX)==1){

                        }
                    }else{
                        printf("IIS发送失败\r\n");
                    }
                }
            }
            xSemaphoreGive( Mutex_wifi_RX );
        }
        vTaskDelay(3 / portTICK_RATE_MS);
    }

最后在创建一个任务将摄像头数据发送出去

xTaskCreate(Tcp_Transmit,"tcp_transmit_task",4096,NULL,2,&Tcp_Transmit_handle);  //创建WIFI发送任务
void Tcp_Transmit(void *pvParameters){
    ESP_LOGI(TAG,"TCP发送任务开始");
    err_t ret;
    for(;;){
        pic = esp_camera_fb_get();
        if(!pic)
        {
            ESP_LOGE(TAG, "Camera Init Failed");
        }
        else{
            rst_count = 0;
            ret =  send(connect_sock, pic->buf,pic->len, 0);
            //ESP_LOGI(TAG, "len = %d",pic->len);
            esp_camera_fb_return(pic);
        }
        vTaskDelay(3 / portTICK_RATE_MS);
    }

}

自此,任务全部完成。

连接完成图。

图传效果图

做的比较仓促,没有拍视频,所以声传没法演示。

五、完整项目

完整项目连接:GitHub - xkjhtxy/ESP32-iis-and-camera

根据引用的信息,ESP32模块具有两组SDMMC接口,但在Arduino core for the ESP32中只使用了其中一组接口。因此,如果你想在Arduino中使用ESP32模块驱动喇叭,你需要确定你使用的是哪一组SDMMC接口。 引用提到了通过Arduino软件的文件菜单找到示例代码的方法。你可以先打开Arduino软件,然后选择文件->示例,再找到与你的需求相关的示例代码。通过查看示例代码和对代码进行修改,你可以实现驱动喇叭的功能。 引用提到了在Arduino IDE和VSCode之间配合使用的方法。你可以先在Arduino IDE中获取TFT_eSPI库文件,然后将该库文件复制到VSCode项目中的lib目录下。具体的步骤是,在Arduino IDE中选择工具->管理库,然后搜索TFT_eSPI并安装。 综上所述,如果你想在Arduino中使用ESP32驱动喇叭,你需要确定你使用的是哪一组SDMMC接口,并通过查找示例代码和对代码进行修改来实现此功能。此外,你还可以使用引用提到的方法,在Arduino IDE和VSCode之间配合使用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【Windows & Esp32】基于 libjpeg-9e 编解码库的视频播放器](https://blog.csdn.net/weixin_42258222/article/details/126446040)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值