header-preview
既然有了屏幕,又有了網絡,那豈不是可以串流了!
序
上篇文章已經展示了如何使用ESP32點亮一塊屏幕,那么這次我們來整個活。利用ESP32來顯示電腦的畫面!如果你用的是其他屏幕也沒關系,只要是ESP32配合TFT_eSPI就可以實現,只是在幀率上會有所區別。
這個鏈接為你展示了在M5StickC上的運行效果
現在是除了游戲性以外,一無所有的原神@bilibili
實現方式
電腦作為發送端,負責發送圖像數據->EPS32作為接收端,負責接收并繪制圖像數據
發送端使用python編寫,使用mss模塊捕獲屏幕畫面,再使用python opencv 編碼為JPG
接收端使用C++編寫,TJpg_Decoder解碼,TFT_eSPI繪制
發送的數據以幀為單位,為了減小幀的體積,將會對每一幀原始位圖數據使用JPG編碼,ESP32接收到數據以后,先對JPG進行解碼,再進行繪制。
一次完整通信流程為:
workflow
精簡的代碼
#ifndef COMMON_MACRO_H_#define COMMON_MACRO_H_// Debug情況下,暫時不去測試串流,也無需連接wifi#define DEBUG/*
======================串流相關======================
*/// 幀數據接收完畢#define FRAMEOK 0x01// 頭部接收完畢,接收幀#define HEADEROK 0x02// 準備完畢,接收頭部 注:0x03無法正常發送// https://www.cnblogs.com/young525/p/5873795.html#define PREPAREOK 0x41/*
======================屏幕相關======================
*/#define SCREEN_WIDTH 240#define SCREEN_HEIGHT 135/*
======================WiFi相關======================
*/#define ssid 'CloseWrt_2.5G'// WiFi 密碼#define password 'have5seeds'#endif
#ifndef STREAMINGCOMPONENT_H_#define STREAMINGCOMPONENT_H_#define IDLES 0#define RUNNING 1#define EXITING 2#include StreamingComponent(WiFiClient &clt, TFT_eSPI &tft); uint8_t status = IDLES; void enter(); void exit(); void loop(); bool drawCallBack(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap);// ~StreamingComponent() {// Serial.printf('~StreamingComponentn');// free(wifiBuffer);// free(headerBuffer);// free(frameSizeBuffer);// };private: // WiFiClient指針 WiFiClient *client; // TFT_eSPI指針 TFT_eSPI *Tft; // 幀率相關 double fps_avg = 0.0; uint32_t sec{}, psec{}; uint16_t fps = 0, frame_count = 0; // 幀率相關 // 執行時間相關 // 函數執行時間 uint32_t cost{}; // 一次loop執行時間 uint32_t loopCost{}; // 緩沖部分 // 幀數據大小 uint16_t size{}; // 已經下載幀數據大小 uint16_t bSize{}; // DMA緩沖相關 // 2020.12.04若出現發送端發送超過wifiFrameSize大小(32kb), // 則會導致出錯,而此處無法分配更大內存。 // 暫時未找到正確開啟SPIRAM方法 // 2020.12.04將圖片壓縮方式從LZO改為jpg const int wifiFrameSize = 1024 * 32; // 頭數據大小 const int headerFrameSize = 10; // 待下載的jpg圖片緩沖 uint8_t *wifiBuffer = (uint8_t *) heap_caps_malloc(wifiFrameSize, MALLOC_CAP_8BIT); // 頭數據緩沖 uint8_t *headerBuffer = (uint8_t *) heap_caps_malloc(headerFrameSize, MALLOC_CAP_8BIT); // 幀數據大小緩沖,用于解析字符串為int uint8_t *frameSizeBuffer = (uint8_t *) heap_caps_malloc(headerFrameSize - 1, MALLOC_CAP_8BIT); // DMA 雙緩沖模式 uint16_t dmaBuffer1[16 * 16]{}; // Toggle buffer for 16*16 MCU block, 512bytes uint16_t dmaBuffer2[16 * 16]{}; // Toggle buffer for 16*16 MCU block, 512bytes uint16_t *dmaBufferPtr = dmaBuffer1; // 當前使用的DMA緩沖 bool dmaBufferSel = 0; /** * 顯示回調,用于Tjpeg * 2020-12-06 */ /** * 接收數據 * 2020-12-01 * size: 5222 bytes * cost: 16 ms */ void onReceiveData();};#endif #include 'StreamingComponent.h'StreamingComponent::StreamingComponent(WiFiClient &clt, TFT_eSPI &tft) { this->client = &clt; this->Tft = &tft; Serial.println('StreamingComponent Constuctor');};void StreamingComponent::enter() { status = RUNNING; };void StreamingComponent::exit() { status = EXITING; };void StreamingComponent::loop() { if (status == RUNNING) { Serial.println('StreamingComponent loop'); loopCost = millis(); onReceiveData(); Serial.printf('fps_avg:%f,loop cost:%d msn', fps_avg, millis() - loopCost); Tft->drawString(String(fps_avg), 0, 0, 2); } else if (status == EXITING) { // 啥也不做 }};bool StreamingComponent::drawCallBack(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) { if (status == RUNNING) { if (y >= SCREEN_HEIGHT) return 0; if (dmaBufferSel) { dmaBufferPtr = dmaBuffer2; } else { dmaBufferPtr = dmaBuffer1; } dmaBufferSel = !dmaBufferSel; Tft->pushImageDMA(x, y, w, h, bitmap, dmaBufferPtr); } return true;}// ~StreamingComponent() {// Serial.printf('~StreamingComponentn');// free(wifiBuffer);// free(headerBuffer);// free(frameSizeBuffer);// };void StreamingComponent::onReceiveData() { Serial.println('StreamingComponent onReceiveData'); StreamingComponent::client->write(PREPAREOK); Serial.println('StreamingComponent client.write(PREPAREOK);'); cost = millis(); if (headerBuffer == nullptr) { Serial.printf('headerBuffer is null.n'); } else { client->readBytes(headerBuffer, headerFrameSize); Serial.printf('receive header cost:%d msn', millis() - cost); } int sum = checkSum((const char *)headerBuffer, 8); // Serial.printf('headerBuffer checkSum: %dn', sum); if ((sum & 0xf) == c2i(headerBuffer[9]) && (sum >> 4) == c2i(headerBuffer[8])) { // 有效頭數據,準備接收幀數據 strncpy((char *)frameSizeBuffer, (char *)headerBuffer, 8); frameSizeBuffer[9] = '
主站蜘蛛池模板:
密山市|
营口市|
宾阳县|
沅陵县|
屏东市|
杨浦区|
嫩江县|
饶河县|
阳原县|
霍林郭勒市|
慈溪市|
梨树县|
柯坪县|
日喀则市|
巴马|
砚山县|
淮安市|
广宗县|
登封市|
桃江县|
贵阳市|
黎城县|
日土县|
南昌县|
拉孜县|
芜湖市|
三台县|
柳河县|
蓬莱市|
望谟县|
丰原市|
邳州市|
积石山|
滦南县|
九龙城区|
简阳市|
竹山县|
东辽县|
都江堰市|
安吉县|
昭觉县|