mirror of
https://github.com/arkhipenko/esp32-mjpeg-multiclient-espcam-drivers.git
synced 2025-01-08 17:11:22 +01:00
Performance tuning. Memory management
This commit is contained in:
parent
ea7a76a216
commit
dcf6bda9c3
19 changed files with 5333 additions and 144 deletions
|
@ -34,7 +34,18 @@ Open workspace file `esp32-cam-rtos-pio\esp32-cam-rtos-pio.code-workspace` with
|
||||||
|
|
||||||
Switch to Platform IO menu then build and upload appropriate camera options
|
Switch to Platform IO menu then build and upload appropriate camera options
|
||||||
|
|
||||||
If your cemera model is not listed - read on or try contracting me.
|
If your cemera model is not listed - read on or try contacting me to create appropriate build option, device file and partition scheme.
|
||||||
|
|
||||||
|
When firmware is running, connect to the WIFI AP it creates:
|
||||||
|
|
||||||
|
- SSID: mjpeg-ap
|
||||||
|
- Password: MJPEG123
|
||||||
|
|
||||||
|
Provide WIFI credentials for your network
|
||||||
|
|
||||||
|
Once connected, the MJPEG stream is available on: Stream Link: http://<your local network IP address>/mjpeg/1
|
||||||
|
|
||||||
|
e.g.: Stream Link: http://192.168.122.132/mjpeg/1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@ typedef struct {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t cnt; // served to clients counter. when equal to number of active clients, could be deleted
|
uint8_t cnt; // served to clients counter. when equal to number of active clients, could be deleted
|
||||||
uint32_t* nxt; // next chunck
|
void* nxt; // next chunck
|
||||||
uint32_t fnm; // frame number
|
uint32_t fnm; // frame number
|
||||||
uint32_t siz; // frame size
|
uint32_t siz; // frame size
|
||||||
uint8_t* dat; // frame pointer
|
uint8_t* dat; // frame pointer
|
||||||
} frameChunck_t;
|
} frameChunck_t;
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,12 @@ void camCB(void* pvParameters);
|
||||||
void handleJPGSstream(void);
|
void handleJPGSstream(void);
|
||||||
void streamCB(void * pvParameters);
|
void streamCB(void * pvParameters);
|
||||||
void mjpegCB(void * pvParameters);
|
void mjpegCB(void * pvParameters);
|
||||||
char* allocateMemory(char* aPtr, size_t aSize, bool psramOnly = false);
|
|
||||||
|
#define FAIL_IF_OOM true
|
||||||
|
#define OK_IF_OOM false
|
||||||
|
#define PSRAM_ONLY true
|
||||||
|
#define ANY_MEMORY false
|
||||||
|
char* allocateMemory(char* aPtr, size_t aSize, bool fail = FAIL_IF_OOM, bool psramOnly = ANY_MEMORY);
|
||||||
|
|
||||||
extern const char* HEADER;
|
extern const char* HEADER;
|
||||||
extern const char* BOUNDARY;
|
extern const char* BOUNDARY;
|
||||||
|
@ -36,4 +41,4 @@ extern const int cntLen;
|
||||||
extern volatile uint32_t frameNumber;
|
extern volatile uint32_t frameNumber;
|
||||||
|
|
||||||
extern frameChunck_t* fstFrame;
|
extern frameChunck_t* fstFrame;
|
||||||
extern frameChunck_t* curFrame;
|
extern frameChunck_t* curFrame;
|
||||||
|
|
|
@ -56,6 +56,8 @@ typedef void (*printfunction)(Print*);
|
||||||
---- Wildcards
|
---- Wildcards
|
||||||
|
|
||||||
%s replace with an string (char*)
|
%s replace with an string (char*)
|
||||||
|
%S replace with a String (String)
|
||||||
|
%I replace with an IP address
|
||||||
%c replace with an character
|
%c replace with an character
|
||||||
%d replace with an integer value
|
%d replace with an integer value
|
||||||
%l replace with an long value
|
%l replace with an long value
|
||||||
|
|
27
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/LICENSE.txt
Normal file
27
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/LICENSE.txt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2015-2020, Anatoli Arkhipenko.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||||
|
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
16
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/README.md
Normal file
16
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Average Filter
|
||||||
|
|
||||||
|
### Digital implementation of an average filter
|
||||||
|
[![arduino-library-badge](https://www.ardu-badge.com/badge/AverageFilter.svg?)](https://www.ardu-badge.com/AverageFilter)
|
||||||
|
|
||||||
|
##### With variable number of tracked values and externally provided storage
|
||||||
|
|
||||||
|
##### Version 1.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###### Changelog:
|
||||||
|
|
||||||
|
v1.0.0:
|
||||||
|
|
||||||
|
- 2015-11-18 - initial release
|
23
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/keywords.txt
Normal file
23
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/keywords.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For TaskManager
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
averageFilter KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
initialize KEYWORD2
|
||||||
|
value KEYWORD2
|
||||||
|
currentValue KEYWORD2
|
||||||
|
samples KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
22
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/library.json
Normal file
22
PlatformIO/esp32-cam-rtos-pio/lib/AverageFilter/library.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "AverageFilter",
|
||||||
|
"keywords": "integer, filter, average, signal, processing",
|
||||||
|
"description": "Integer implementation of the Average Filter",
|
||||||
|
"repository":
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/arkhipenko/AverageFilter"
|
||||||
|
},
|
||||||
|
"authors":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Anatoli Arkhipenko",
|
||||||
|
"email": "arkhipenko@hotmail.com",
|
||||||
|
"url": "https://github.com/arkhipenko",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "1.0.1",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": "*"
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
//
|
||||||
|
// Digital implementation of an average filter
|
||||||
|
// with variable number of samples
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#ifndef _AVERAGEFILTER_H
|
||||||
|
#define _AVERAGEFILTER_H
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class averageFilter {
|
||||||
|
public:
|
||||||
|
averageFilter(int aSamples);
|
||||||
|
averageFilter(int aSamples, T* aStorage);
|
||||||
|
~averageFilter();
|
||||||
|
|
||||||
|
void initialize();
|
||||||
|
T value(T aSample);
|
||||||
|
void setSamples( int aSamples );
|
||||||
|
|
||||||
|
inline T currentValue() { return iCV; }
|
||||||
|
inline int32_t samples() { return iCount; }
|
||||||
|
inline bool memoryError() { return iReadings == NULL; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T *iReadings;
|
||||||
|
T iTotal;
|
||||||
|
T iCV;
|
||||||
|
int32_t iIndex;
|
||||||
|
int32_t iCount;
|
||||||
|
int32_t iSamples;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
averageFilter<T>::averageFilter(int aSamples) {
|
||||||
|
iSamples = constrain(aSamples, 1, aSamples);
|
||||||
|
iReadings = (T *) malloc (sizeof (T) * aSamples);
|
||||||
|
// if there is a memory allocation error.
|
||||||
|
if (iReadings == NULL) {
|
||||||
|
iReadings = &iCV;
|
||||||
|
iSamples = 1;
|
||||||
|
}
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
averageFilter<T>::averageFilter(int aSamples, T* aStorage) {
|
||||||
|
iSamples = constrain(aSamples, 1, aSamples);
|
||||||
|
iReadings = aStorage;
|
||||||
|
// if storage provided is NULL
|
||||||
|
if (iReadings == NULL) {
|
||||||
|
iReadings = &iCV;
|
||||||
|
iSamples = 1;
|
||||||
|
}
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void averageFilter<T>::setSamples(int aSamples) {
|
||||||
|
iSamples = constrain(aSamples, 1, aSamples);
|
||||||
|
if ( iCount ) initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
averageFilter<T>::~averageFilter() {
|
||||||
|
if ( iReadings && iReadings != &iCV ) free (iReadings);
|
||||||
|
iReadings = NULL;
|
||||||
|
iIndex = 0;
|
||||||
|
iTotal = 0;
|
||||||
|
iCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void averageFilter<T>::initialize() {
|
||||||
|
iIndex = 0;
|
||||||
|
iTotal = 0;
|
||||||
|
iCount = 0;
|
||||||
|
iCV = 0;
|
||||||
|
for (int i=0; i<iSamples; i++) iReadings[i]=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T averageFilter<T>::value(T aSample) {
|
||||||
|
// if ( !iReadings ) return 0;
|
||||||
|
iTotal -= iReadings[iIndex];
|
||||||
|
iReadings[iIndex] = aSample;
|
||||||
|
iTotal += aSample;
|
||||||
|
if (++iIndex >= iSamples) iIndex = 0;
|
||||||
|
if (++iCount > iSamples) iCount = iSamples;
|
||||||
|
iCV = (T) iTotal/iCount;
|
||||||
|
return iCV;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _AVERAGEFILTER_H
|
|
@ -9,7 +9,7 @@
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = esp-eye-debug
|
default_envs = ai-thinker-cam-debug ;esp-eye-debug
|
||||||
boards_dir = ./boards
|
boards_dir = ./boards
|
||||||
src_dir = src
|
src_dir = src
|
||||||
lib_dir = lib
|
lib_dir = lib
|
||||||
|
@ -76,14 +76,46 @@
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
-D CAMERA_MODEL_AI_THINKER
|
-D CAMERA_MODEL_AI_THINKER
|
||||||
-D FRAME_SIZE=FRAMESIZE_HVGA
|
-D FRAME_SIZE=FRAMESIZE_HVGA
|
||||||
-D FPS=5
|
-D FPS=10
|
||||||
-D WSINTERVAL=40
|
-D WSINTERVAL=100
|
||||||
-D MAX_CLIENTS=10
|
-D MAX_CLIENTS=10
|
||||||
-D JPEG_QUALITY=24 ; 0-63 lower means higher quality
|
-D JPEG_QUALITY=16 ; 0-63 lower means higher quality
|
||||||
-D LOG_LEVEL=0
|
-D LOG_LEVEL=0
|
||||||
-D DISABLE_LOGGING
|
-D DISABLE_LOGGING
|
||||||
-D WM_NODEBUG
|
-D WM_NODEBUG
|
||||||
|
|
||||||
|
|
||||||
|
[env:ai-thinker-cam-debug]
|
||||||
|
; ESP32-CAM AI-Thinker
|
||||||
|
; https://docs.platformio.org/en/stable/boards/espressif32/esp32cam.html'
|
||||||
|
; https://docs.ai-thinker.com/en/esp32
|
||||||
|
; board has 4MB PSRAM
|
||||||
|
build_type = debug
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino, espidf
|
||||||
|
board = ai-thinker-cam
|
||||||
|
board_build.partitions = ai-thinker-cam.csv
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
|
||||||
|
lib_ldf_mode = deep+
|
||||||
|
|
||||||
|
upload_speed = 921600
|
||||||
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D CAMERA_MODEL_AI_THINKER
|
||||||
|
-D FRAME_SIZE=FRAMESIZE_HVGA
|
||||||
|
-D FPS=10
|
||||||
|
-D WSINTERVAL=100
|
||||||
|
-D MAX_CLIENTS=10
|
||||||
|
-D JPEG_QUALITY=16 ; 0-63 lower means higher quality
|
||||||
|
-D LOG_LEVEL=6
|
||||||
|
-D BENCHMARK
|
||||||
|
|
||||||
|
|
||||||
[env:esp-eye]
|
[env:esp-eye]
|
||||||
; Espressif ESP-EYE
|
; Espressif ESP-EYE
|
||||||
; https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP-EYE_Getting_Started_Guide.md
|
; https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP-EYE_Getting_Started_Guide.md
|
||||||
|
@ -108,10 +140,10 @@
|
||||||
-D CAMERA_MODEL_ESP_EYE
|
-D CAMERA_MODEL_ESP_EYE
|
||||||
; -D FLIP_VERTICALLY
|
; -D FLIP_VERTICALLY
|
||||||
-D FRAME_SIZE=FRAMESIZE_VGA
|
-D FRAME_SIZE=FRAMESIZE_VGA
|
||||||
-D FPS=15
|
-D FPS=10
|
||||||
-D WSINTERVAL=40
|
-D WSINTERVAL=100
|
||||||
-D MAX_CLIENTS=10
|
-D MAX_CLIENTS=10
|
||||||
-D JPEG_QUALITY=16 ; 0-63 lower means higher quality
|
-D JPEG_QUALITY=24 ; 0-63 lower means higher quality
|
||||||
-D LOG_LEVEL=0
|
-D LOG_LEVEL=0
|
||||||
-D DISABLE_LOGGING
|
-D DISABLE_LOGGING
|
||||||
-D WM_NODEBUG
|
-D WM_NODEBUG
|
||||||
|
@ -140,14 +172,14 @@
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
-D CAMERA_MODEL_ESP_EYE
|
-D CAMERA_MODEL_ESP_EYE
|
||||||
-D FLIP_VERTICALLY
|
-D FLIP_VERTICALLY
|
||||||
-D FRAME_SIZE=FRAMESIZE_VGA
|
-D FRAME_SIZE=FRAMESIZE_SVGA
|
||||||
-D FPS=15
|
-D FPS=10
|
||||||
-D WSINTERVAL=40
|
-D WSINTERVAL=100
|
||||||
-D MAX_CLIENTS=10
|
-D MAX_CLIENTS=10
|
||||||
-D JPEG_QUALITY=16 ; 0-63 lower means higher quality
|
-D JPEG_QUALITY=32 ; 0-63 lower means higher quality
|
||||||
-D LOG_LEVEL=6
|
-D LOG_LEVEL=6
|
||||||
-D WM_DEBUG_LEVEL=WM_DEBUG_VERBOSE
|
-D WM_DEBUG_LEVEL=WM_DEBUG_VERBOSE
|
||||||
|
-D BENCHMARK
|
||||||
|
|
||||||
|
|
||||||
; EXAMPLE of the additional board configuration
|
; EXAMPLE of the additional board configuration
|
||||||
|
@ -179,7 +211,7 @@
|
||||||
; -D FLIP_VERTICALLY
|
; -D FLIP_VERTICALLY
|
||||||
-D FRAME_SIZE=FRAMESIZE_HVGA
|
-D FRAME_SIZE=FRAMESIZE_HVGA
|
||||||
-D FPS=5
|
-D FPS=5
|
||||||
-D WSINTERVAL=40
|
-D WSINTERVAL=100
|
||||||
-D MAX_CLIENTS=10
|
-D MAX_CLIENTS=10
|
||||||
-D JPEG_QUALITY=24 ; 0-63 lower means higher quality
|
-D JPEG_QUALITY=24 ; 0-63 lower means higher quality
|
||||||
-D LOG_LEVEL=0
|
-D LOG_LEVEL=0
|
||||||
|
|
1651
PlatformIO/esp32-cam-rtos-pio/sdkconfig.ai-thinker-cam-debug
Normal file
1651
PlatformIO/esp32-cam-rtos-pio/sdkconfig.ai-thinker-cam-debug
Normal file
File diff suppressed because it is too large
Load diff
1493
PlatformIO/esp32-cam-rtos-pio/sdkconfig.ai-thinker-cam-debug.old
Normal file
1493
PlatformIO/esp32-cam-rtos-pio/sdkconfig.ai-thinker-cam-debug.old
Normal file
File diff suppressed because it is too large
Load diff
|
@ -50,8 +50,16 @@ CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
|
||||||
# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set
|
# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set
|
||||||
# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set
|
# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set
|
||||||
CONFIG_BOOTLOADER_LOG_LEVEL=0
|
CONFIG_BOOTLOADER_LOG_LEVEL=0
|
||||||
|
|
||||||
|
#
|
||||||
|
# Serial Flash Configurations
|
||||||
|
#
|
||||||
# CONFIG_BOOTLOADER_SPI_CUSTOM_WP_PIN is not set
|
# CONFIG_BOOTLOADER_SPI_CUSTOM_WP_PIN is not set
|
||||||
CONFIG_BOOTLOADER_SPI_WP_PIN=7
|
CONFIG_BOOTLOADER_SPI_WP_PIN=7
|
||||||
|
# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set
|
||||||
|
CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y
|
||||||
|
# end of Serial Flash Configurations
|
||||||
|
|
||||||
CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y
|
CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y
|
||||||
# CONFIG_BOOTLOADER_FACTORY_RESET is not set
|
# CONFIG_BOOTLOADER_FACTORY_RESET is not set
|
||||||
# CONFIG_BOOTLOADER_APP_TEST is not set
|
# CONFIG_BOOTLOADER_APP_TEST is not set
|
||||||
|
@ -65,7 +73,6 @@ CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
|
||||||
# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set
|
# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set
|
||||||
CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x10
|
CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x10
|
||||||
# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set
|
# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set
|
||||||
CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y
|
|
||||||
# end of Bootloader config
|
# end of Bootloader config
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -581,6 +588,7 @@ CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
|
||||||
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
|
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
|
||||||
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
|
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
|
||||||
# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set
|
# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set
|
||||||
|
CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y
|
||||||
# end of Sleep Config
|
# end of Sleep Config
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -601,6 +609,10 @@ CONFIG_ESP_IPC_ISR_ENABLE=y
|
||||||
# LCD and Touch Panel
|
# LCD and Touch Panel
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# LCD Touch Drivers are maintained in the IDF Component Registry
|
||||||
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
# LCD Peripheral Configuration
|
# LCD Peripheral Configuration
|
||||||
#
|
#
|
||||||
|
@ -710,6 +722,10 @@ CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
|
||||||
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
|
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
|
||||||
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16
|
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16
|
||||||
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32
|
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32
|
||||||
|
CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y
|
||||||
|
# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set
|
||||||
|
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0
|
||||||
|
CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5
|
||||||
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
|
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
|
||||||
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
|
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
|
||||||
CONFIG_ESP32_WIFI_TX_BA_WIN=6
|
CONFIG_ESP32_WIFI_TX_BA_WIN=6
|
||||||
|
@ -905,6 +921,7 @@ CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
|
||||||
#
|
#
|
||||||
CONFIG_LWIP_LOCAL_HOSTNAME="espressif"
|
CONFIG_LWIP_LOCAL_HOSTNAME="espressif"
|
||||||
# CONFIG_LWIP_NETIF_API is not set
|
# CONFIG_LWIP_NETIF_API is not set
|
||||||
|
CONFIG_LWIP_TCPIP_TASK_PRIO=18
|
||||||
# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set
|
# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set
|
||||||
# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set
|
# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set
|
||||||
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||||
|
@ -918,6 +935,7 @@ CONFIG_LWIP_SO_REUSE=y
|
||||||
CONFIG_LWIP_SO_REUSE_RXTOALL=y
|
CONFIG_LWIP_SO_REUSE_RXTOALL=y
|
||||||
CONFIG_LWIP_SO_RCVBUF=y
|
CONFIG_LWIP_SO_RCVBUF=y
|
||||||
# CONFIG_LWIP_NETBUF_RECVINFO is not set
|
# CONFIG_LWIP_NETBUF_RECVINFO is not set
|
||||||
|
CONFIG_LWIP_IP_DEFAULT_TTL=64
|
||||||
CONFIG_LWIP_IP4_FRAG=y
|
CONFIG_LWIP_IP4_FRAG=y
|
||||||
CONFIG_LWIP_IP6_FRAG=y
|
CONFIG_LWIP_IP6_FRAG=y
|
||||||
# CONFIG_LWIP_IP4_REASSEMBLY is not set
|
# CONFIG_LWIP_IP4_REASSEMBLY is not set
|
||||||
|
@ -970,6 +988,8 @@ CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744
|
||||||
CONFIG_LWIP_TCP_WND_DEFAULT=5744
|
CONFIG_LWIP_TCP_WND_DEFAULT=5744
|
||||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=6
|
CONFIG_LWIP_TCP_RECVMBOX_SIZE=6
|
||||||
CONFIG_LWIP_TCP_QUEUE_OOSEQ=y
|
CONFIG_LWIP_TCP_QUEUE_OOSEQ=y
|
||||||
|
CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6
|
||||||
|
CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=0
|
||||||
# CONFIG_LWIP_TCP_SACK_OUT is not set
|
# CONFIG_LWIP_TCP_SACK_OUT is not set
|
||||||
# CONFIG_LWIP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set
|
# CONFIG_LWIP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set
|
||||||
CONFIG_LWIP_TCP_OVERSIZE_MSS=y
|
CONFIG_LWIP_TCP_OVERSIZE_MSS=y
|
||||||
|
@ -1026,6 +1046,13 @@ CONFIG_LWIP_SNTP_MAX_SERVERS=1
|
||||||
CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000
|
CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000
|
||||||
# end of SNTP
|
# end of SNTP
|
||||||
|
|
||||||
|
#
|
||||||
|
# DNS
|
||||||
|
#
|
||||||
|
CONFIG_LWIP_DNS_MAX_SERVERS=3
|
||||||
|
# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set
|
||||||
|
# end of DNS
|
||||||
|
|
||||||
#
|
#
|
||||||
# Hooks
|
# Hooks
|
||||||
#
|
#
|
||||||
|
@ -1254,6 +1281,20 @@ CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1
|
||||||
CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread"
|
CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread"
|
||||||
# end of PThreads
|
# end of PThreads
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main Flash configuration
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Optional and Experimental Features (READ DOCS FIRST)
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Features here require specific hardware (READ DOCS FIRST!)
|
||||||
|
#
|
||||||
|
# end of Optional and Experimental Features (READ DOCS FIRST)
|
||||||
|
# end of Main Flash configuration
|
||||||
|
|
||||||
#
|
#
|
||||||
# SPI Flash driver
|
# SPI Flash driver
|
||||||
#
|
#
|
||||||
|
|
|
@ -39,8 +39,8 @@ CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16
|
||||||
# Bootloader config
|
# Bootloader config
|
||||||
#
|
#
|
||||||
CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000
|
CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000
|
||||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE is not set
|
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG=y
|
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set
|
||||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set
|
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set
|
||||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set
|
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set
|
||||||
# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set
|
# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set
|
||||||
|
|
1493
PlatformIO/esp32-cam-rtos-pio/sdkconfig.esp-eye-debug.old
Normal file
1493
PlatformIO/esp32-cam-rtos-pio/sdkconfig.esp-eye-debug.old
Normal file
File diff suppressed because it is too large
Load diff
|
@ -81,7 +81,10 @@ void setup()
|
||||||
setupLogging();
|
setupLogging();
|
||||||
|
|
||||||
Log.trace("\n\nMulti-client MJPEG Server\n");
|
Log.trace("\n\nMulti-client MJPEG Server\n");
|
||||||
|
Log.trace("setup: total heap : %d\n", ESP.getHeapSize());
|
||||||
Log.trace("setup: free heap : %d\n", ESP.getFreeHeap());
|
Log.trace("setup: free heap : %d\n", ESP.getFreeHeap());
|
||||||
|
Log.trace("setup: free psram : %d\n", ESP.getPsramSize());
|
||||||
|
Log.trace("setup: free psram : %d\n", ESP.getFreePsram());
|
||||||
|
|
||||||
static camera_config_t camera_config = {
|
static camera_config_t camera_config = {
|
||||||
.pin_pwdn = PWDN_GPIO_NUM,
|
.pin_pwdn = PWDN_GPIO_NUM,
|
||||||
|
@ -101,13 +104,14 @@ void setup()
|
||||||
.pin_href = HREF_GPIO_NUM,
|
.pin_href = HREF_GPIO_NUM,
|
||||||
.pin_pclk = PCLK_GPIO_NUM,
|
.pin_pclk = PCLK_GPIO_NUM,
|
||||||
|
|
||||||
|
// .xclk_freq_hz = 16000000,
|
||||||
.xclk_freq_hz = 20000000,
|
.xclk_freq_hz = 20000000,
|
||||||
.ledc_timer = LEDC_TIMER_0,
|
.ledc_timer = LEDC_TIMER_0,
|
||||||
.ledc_channel = LEDC_CHANNEL_0,
|
.ledc_channel = LEDC_CHANNEL_0,
|
||||||
.pixel_format = PIXFORMAT_JPEG,
|
.pixel_format = PIXFORMAT_JPEG,
|
||||||
.frame_size = FRAME_SIZE,
|
.frame_size = FRAME_SIZE,
|
||||||
.jpeg_quality = JPEG_QUALITY,
|
.jpeg_quality = JPEG_QUALITY,
|
||||||
.fb_count = 2,
|
.fb_count = 1, //2,
|
||||||
.fb_location = CAMERA_FB_IN_DRAM,
|
.fb_location = CAMERA_FB_IN_DRAM,
|
||||||
.grab_mode = CAMERA_GRAB_LATEST,
|
.grab_mode = CAMERA_GRAB_LATEST,
|
||||||
// .sccb_i2c_port = -1
|
// .sccb_i2c_port = -1
|
||||||
|
@ -166,7 +170,7 @@ void setup()
|
||||||
NULL,
|
NULL,
|
||||||
2,
|
2,
|
||||||
&tMjpeg,
|
&tMjpeg,
|
||||||
APP_CPU);
|
PRO_CPU);
|
||||||
|
|
||||||
Log.trace("setup complete: free heap : %d\n", ESP.getFreeHeap());
|
Log.trace("setup complete: free heap : %d\n", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,8 @@ const int bdrLen = strlen(BOUNDARY);
|
||||||
const int cntLen = strlen(CTNTTYPE);
|
const int cntLen = strlen(CTNTTYPE);
|
||||||
volatile uint32_t frameNumber;
|
volatile uint32_t frameNumber;
|
||||||
|
|
||||||
|
frameChunck_t* fstFrame = NULL; // first frame
|
||||||
frameChunck_t* fstFrame; // first frame
|
frameChunck_t* curFrame = NULL; // current frame being captured by the camera
|
||||||
frameChunck_t* curFrame; // current frame being captured by the camera
|
|
||||||
|
|
||||||
void mjpegCB(void* pvParameters) {
|
void mjpegCB(void* pvParameters) {
|
||||||
TickType_t xLastWakeTime;
|
TickType_t xLastWakeTime;
|
||||||
|
@ -49,16 +48,19 @@ void mjpegCB(void* pvParameters) {
|
||||||
server.handleClient();
|
server.handleClient();
|
||||||
|
|
||||||
// After every server client handling request, we let other tasks run and then pause
|
// After every server client handling request, we let other tasks run and then pause
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==== Memory allocator that takes advantage of PSRAM if present =======================
|
// ==== Memory allocator that takes advantage of PSRAM if present =======================
|
||||||
char* allocateMemory(char* aPtr, size_t aSize, bool psramOnly) {
|
char* allocateMemory(char* aPtr, size_t aSize, bool fail, bool psramOnly) {
|
||||||
|
|
||||||
// Since current buffer is too smal, free it
|
// Since current buffer is too smal, free it
|
||||||
if (aPtr != NULL) free(aPtr);
|
if (aPtr != NULL) {
|
||||||
|
free(aPtr);
|
||||||
|
aPtr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
char* ptr = NULL;
|
char* ptr = NULL;
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ char* allocateMemory(char* aPtr, size_t aSize, bool psramOnly) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finally, if the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition.
|
// Finally, if the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition.
|
||||||
if (ptr == NULL) {
|
if (fail && ptr == NULL) {
|
||||||
Log.fatal("allocateMemory: Out of memory!");
|
Log.fatal("allocateMemory: Out of memory!");
|
||||||
delay(5000);
|
delay(5000);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
#if defined (CAMERA_ALL_FRAMES)
|
#if defined (CAMERA_ALL_FRAMES)
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
#include <AverageFilter.h>
|
||||||
|
#define BENCHMARK_PRINT_INT 1000
|
||||||
|
#endif
|
||||||
|
|
||||||
// ==== RTOS task to grab frames from the camera =========================
|
// ==== RTOS task to grab frames from the camera =========================
|
||||||
void camCB(void* pvParameters) {
|
void camCB(void* pvParameters) {
|
||||||
|
@ -14,55 +18,100 @@ void camCB(void* pvParameters) {
|
||||||
frameNumber = 0;
|
frameNumber = 0;
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
averageFilter<uint32_t> captureAvg(10);
|
||||||
|
averageFilter<uint32_t> tickAvg(10);
|
||||||
|
captureAvg.initialize();
|
||||||
|
tickAvg.initialize();
|
||||||
|
uint32_t lastPrintCam = millis();
|
||||||
|
uint32_t benchmarkStart;
|
||||||
|
uint32_t lastTick = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
camera_fb_t* fb = NULL;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
camera_fb_t* fb = NULL;
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
benchmarkStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Grab a frame from the camera and allocate frame chunk for it
|
// Grab a frame from the camera and allocate frame chunk for it
|
||||||
fb = esp_camera_fb_get();
|
fb = esp_camera_fb_get();
|
||||||
frameChunck_t* f = (frameChunck_t*) allocateMemory(NULL, sizeof(frameChunck_t), true);
|
if ( fb ) {
|
||||||
if ( f ) {
|
frameChunck_t* f = (frameChunck_t*) allocateMemory(NULL, sizeof(frameChunck_t), OK_IF_OOM, PSRAM_ONLY);
|
||||||
// char* d = (char*) ps_malloc( fb->len );
|
if ( f ) {
|
||||||
char* d = (char*) allocateMemory(NULL, fb->len, true);
|
// char* d = (char*) ps_malloc( fb->len );
|
||||||
if ( d == NULL ) {
|
char* d = (char*) allocateMemory(NULL, fb->len, OK_IF_OOM, PSRAM_ONLY);
|
||||||
free (f);
|
if ( d == NULL ) {
|
||||||
|
free (f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( fstFrame == NULL ) {
|
||||||
|
fstFrame = f;
|
||||||
|
}
|
||||||
|
f->dat = (uint8_t*) d;
|
||||||
|
f->nxt = NULL;
|
||||||
|
f->siz = fb->len;
|
||||||
|
f->cnt = 0;
|
||||||
|
memcpy(f->dat, (char *)fb->buf, fb->len);
|
||||||
|
f->fnm = frameNumber;
|
||||||
|
if ( curFrame ) {
|
||||||
|
curFrame->nxt = (uint32_t*) f;
|
||||||
|
}
|
||||||
|
curFrame = f;
|
||||||
|
// Log.verbose("Captured frame# %d\n", frameNumber);
|
||||||
|
frameNumber++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( frameNumber == 0 ) {
|
Log.error("camCB: error allocating memory for frame %d - OOM\n", frameNumber);
|
||||||
fstFrame = f;
|
vTaskDelay(1000);
|
||||||
}
|
|
||||||
f->dat = (uint8_t*) d;
|
|
||||||
f->nxt = NULL;
|
|
||||||
f->siz = fb->len;
|
|
||||||
f->cnt = 0;
|
|
||||||
memcpy(f->dat, (char *)fb->buf, fb->len);
|
|
||||||
f->fnm = frameNumber;
|
|
||||||
if ( curFrame ) {
|
|
||||||
curFrame->nxt = (uint32_t*) f;
|
|
||||||
}
|
|
||||||
curFrame = f;
|
|
||||||
// Log.verbose("Captured frame# %d\n", frameNumber);
|
|
||||||
frameNumber++;
|
|
||||||
}
|
}
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.error("camCB: error capturing image for frame %d\n", frameNumber);
|
||||||
|
vTaskDelay(1000);
|
||||||
}
|
}
|
||||||
esp_camera_fb_return(fb);
|
|
||||||
|
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
#if defined(BENCHMARK)
|
||||||
|
captureAvg.value(micros()-benchmarkStart);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
tickAvg.value(millis()-lastTick);
|
||||||
|
lastTick = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
if ( noActiveClients == 0 ) {
|
if ( noActiveClients == 0 ) {
|
||||||
// we need to drain the cache if there are no more clients connected
|
// we need to drain the cache if there are no more clients connected
|
||||||
Log.trace("mjpegCB: All clients disconneted\n");
|
Log.trace("mjpegCB: All clients disconneted\n");
|
||||||
while ( fstFrame->nxt ) {
|
while ( fstFrame !=NULL && fstFrame->nxt ) {
|
||||||
frameChunck_t* f = (frameChunck_t*) fstFrame->nxt;
|
frameChunck_t* f = (frameChunck_t*) fstFrame->nxt;
|
||||||
free ( fstFrame->dat );
|
free ( fstFrame->dat );
|
||||||
free ( fstFrame );
|
free ( fstFrame );
|
||||||
fstFrame = f;
|
fstFrame = f;
|
||||||
}
|
}
|
||||||
|
fstFrame = NULL;
|
||||||
|
curFrame = NULL;
|
||||||
Log.verbose("mjpegCB: free heap : %d\n", ESP.getFreeHeap());
|
Log.verbose("mjpegCB: free heap : %d\n", ESP.getFreeHeap());
|
||||||
Log.verbose("mjpegCB: min free heap) : %d\n", ESP.getMinFreeHeap());
|
Log.verbose("mjpegCB: min free heap : %d\n", ESP.getMinFreeHeap());
|
||||||
Log.verbose("mjpegCB: max alloc free heap : %d\n", ESP.getMaxAllocHeap());
|
Log.verbose("mjpegCB: max alloc free heap : %d\n", ESP.getMaxAllocHeap());
|
||||||
|
Log.verbose("mjpegCB: free psram : %d\n", ESP.getFreePsram());
|
||||||
Log.verbose("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam));
|
Log.verbose("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam));
|
||||||
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
if ( millis() - lastPrintCam > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrintCam = millis();
|
||||||
|
Log.verbose("mjpegCB: average frame capture time: %d us (tick avg=%d)\n", captureAvg.currentValue(), tickAvg.currentValue() );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +123,17 @@ void handleJPGSstream(void)
|
||||||
Log.verbose("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap());
|
Log.verbose("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap());
|
||||||
|
|
||||||
streamInfo_t* info = (streamInfo_t*) malloc( sizeof(streamInfo_t) );
|
streamInfo_t* info = (streamInfo_t*) malloc( sizeof(streamInfo_t) );
|
||||||
|
if ( info == NULL ) {
|
||||||
|
Log.error("handleJPGSstream: cannot allocate stream info - OOM\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
info->client = new WiFiClient;
|
info->client = new WiFiClient;
|
||||||
|
if ( info->client == NULL ) {
|
||||||
|
Log.error("handleJPGSstream: cannot allocate WiFi client for streaming - OOM\n");
|
||||||
|
free(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
*(info->client) = server.client();
|
*(info->client) = server.client();
|
||||||
|
|
||||||
// Creating task to push the stream to all connected clients
|
// Creating task to push the stream to all connected clients
|
||||||
|
@ -92,7 +151,9 @@ void handleJPGSstream(void)
|
||||||
delete info;
|
delete info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
noActiveClients++;
|
noActiveClients++;
|
||||||
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
// Wake up streaming tasks, if they were previously suspended:
|
// Wake up streaming tasks, if they were previously suspended:
|
||||||
if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam );
|
if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam );
|
||||||
|
@ -104,16 +165,11 @@ void streamCB(void * pvParameters) {
|
||||||
char buf[16];
|
char buf[16];
|
||||||
TickType_t xLastWakeTime;
|
TickType_t xLastWakeTime;
|
||||||
TickType_t xFrequency;
|
TickType_t xFrequency;
|
||||||
|
|
||||||
frameChunck_t* myFrame = fstFrame;
|
frameChunck_t* myFrame = fstFrame;
|
||||||
portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED;
|
|
||||||
|
|
||||||
streamInfo_t* info = (streamInfo_t*) pvParameters;
|
streamInfo_t* info = (streamInfo_t*) pvParameters;
|
||||||
|
|
||||||
if ( info == NULL ) {
|
|
||||||
Log.fatal("streamCB: a NULL pointer passed\n");
|
|
||||||
delay(5000);
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
// Immediately send this client a header
|
// Immediately send this client a header
|
||||||
info->client->write(HEADER, hdrLen);
|
info->client->write(HEADER, hdrLen);
|
||||||
info->client->write(BOUNDARY, bdrLen);
|
info->client->write(BOUNDARY, bdrLen);
|
||||||
|
@ -122,53 +178,95 @@ void streamCB(void * pvParameters) {
|
||||||
xFrequency = pdMS_TO_TICKS(1000 / FPS);
|
xFrequency = pdMS_TO_TICKS(1000 / FPS);
|
||||||
|
|
||||||
Log.trace("streamCB: Client connected\n");
|
Log.trace("streamCB: Client connected\n");
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
averageFilter<int32_t> streamAvg(10);
|
||||||
|
averageFilter<int32_t> waitAvg(10);
|
||||||
|
averageFilter<uint32_t> frameAvg(10);
|
||||||
|
averageFilter<float> fpsAvg(10);
|
||||||
|
uint32_t streamStart = 0;
|
||||||
|
streamAvg.initialize();
|
||||||
|
waitAvg.initialize();
|
||||||
|
frameAvg.initialize();
|
||||||
|
fpsAvg.initialize();
|
||||||
|
uint32_t lastPrint = millis();
|
||||||
|
uint32_t lastFrame = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Only bother to send anything if there is someone watching
|
if ( myFrame ) {
|
||||||
if ( info->client->connected() ) {
|
|
||||||
if ( myFrame ) {
|
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
frameAvg.value( myFrame->siz );
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ( info->client->connected() ) {
|
||||||
|
sprintf(buf, "%d\r\n\r\n", myFrame->siz);
|
||||||
info->client->write(CTNTTYPE, cntLen);
|
info->client->write(CTNTTYPE, cntLen);
|
||||||
sprintf(buf, "%d\r\n\r\n", fstFrame->siz);
|
|
||||||
info->client->write(buf, strlen(buf));
|
info->client->write(buf, strlen(buf));
|
||||||
info->client->write((char*) fstFrame->dat, (size_t)fstFrame->siz);
|
info->client->write((char*) myFrame->dat, (size_t)myFrame->siz);
|
||||||
info->client->write(BOUNDARY, bdrLen);
|
info->client->write(BOUNDARY, bdrLen);
|
||||||
|
|
||||||
// Log.verbose("streamCB: Served frame# %d\n", fstFrame->fnm);
|
// Log.verbose("streamCB: Served frame# %d\n", fstFrame->fnm);
|
||||||
|
|
||||||
if ( myFrame->nxt ) {
|
|
||||||
frameChunck_t* f;
|
|
||||||
f = (frameChunck_t*) myFrame->nxt;
|
|
||||||
|
|
||||||
portENTER_CRITICAL(&xSemaphore);
|
|
||||||
if ( ++myFrame->cnt == noActiveClients ) {
|
|
||||||
assert(myFrame == fstFrame);
|
|
||||||
free ( fstFrame->dat );
|
|
||||||
fstFrame->dat = NULL;
|
|
||||||
free ( fstFrame );
|
|
||||||
fstFrame = f;
|
|
||||||
}
|
|
||||||
portEXIT_CRITICAL(&xSemaphore);
|
|
||||||
myFrame = f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
myFrame = fstFrame;
|
#if defined (BENCHMARK)
|
||||||
|
streamAvg.value(micros()-streamStart);
|
||||||
|
fpsAvg.value(1000.0 / (float) (millis()-lastFrame) );
|
||||||
|
lastFrame = millis();
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
waitAvg.value(micros()-streamStart);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
myFrame->cnt++;
|
||||||
|
frameChunck_t* myNextFrame = (frameChunck_t*) myFrame->nxt; // this maybe NULL - that's ok
|
||||||
|
|
||||||
|
// if serving first frame in the chain, check if we are the last serving task and it needs to be deleted
|
||||||
|
if ( myFrame == fstFrame && fstFrame->cnt >= noActiveClients ) {
|
||||||
|
free ( fstFrame->dat );
|
||||||
|
fstFrame->dat = NULL;
|
||||||
|
free ( fstFrame );
|
||||||
|
fstFrame = myNextFrame;
|
||||||
}
|
}
|
||||||
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
|
myFrame = myNextFrame;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
myFrame = fstFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !info->client->connected() ) {
|
||||||
// client disconnected - clean up.
|
// client disconnected - clean up.
|
||||||
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
noActiveClients--;
|
noActiveClients--;
|
||||||
Log.trace("streamCB: Client disconnected\n");
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
Log.verbose("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task));
|
Log.verbose("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task));
|
||||||
info->client->stop();
|
info->client->stop();
|
||||||
delete info->client;
|
delete info->client;
|
||||||
info->client = NULL;
|
info->client = NULL;
|
||||||
free( info );
|
free( info );
|
||||||
info = NULL;
|
info = NULL;
|
||||||
|
Log.trace("streamCB: Client disconnected\n");
|
||||||
|
vTaskDelay(100);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
// Let other tasks run after serving every client
|
// Let other tasks run after serving every client
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
if ( millis() - lastPrint > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrint = millis();
|
||||||
|
Log.verbose("streamCB: wait avg=%d, stream avg=%d us, frame avg size=%d bytes, fps=%S\n", waitAvg.currentValue(), streamAvg.currentValue(), frameAvg.currentValue(), String(fpsAvg.currentValue()));
|
||||||
|
if ( fstFrame ) Log.verbose("streamCB: current frame: %d, first frame:%d\n", curFrame->fnm, fstFrame->fnm);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
#include "streaming.h"
|
#include "streaming.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
#if defined(CAMERA_MULTICLIENT_QUEUE)
|
#if defined(CAMERA_MULTICLIENT_QUEUE)
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
#include <AverageFilter.h>
|
||||||
|
#define BENCHMARK_PRINT_INT 1000
|
||||||
|
averageFilter<uint32_t> captureAvg(10);
|
||||||
|
uint32_t lastPrintCam = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
QueueHandle_t streamingClients;
|
QueueHandle_t streamingClients;
|
||||||
|
|
||||||
volatile size_t camSize; // size of the current frame, byte
|
volatile size_t camSize; // size of the current frame, byte
|
||||||
|
@ -14,9 +22,6 @@ void camCB(void* pvParameters) {
|
||||||
// A running interval associated with currently desired frame rate
|
// A running interval associated with currently desired frame rate
|
||||||
const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS);
|
const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS);
|
||||||
|
|
||||||
// Mutex for the critical section of swithing the active frames around
|
|
||||||
portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED;
|
|
||||||
|
|
||||||
// Creating a queue to track all connected clients
|
// Creating a queue to track all connected clients
|
||||||
streamingClients = xQueueCreate( 10, sizeof(WiFiClient*) );
|
streamingClients = xQueueCreate( 10, sizeof(WiFiClient*) );
|
||||||
|
|
||||||
|
@ -29,8 +34,8 @@ void camCB(void* pvParameters) {
|
||||||
NULL, //(void*) handler,
|
NULL, //(void*) handler,
|
||||||
2,
|
2,
|
||||||
&tStream,
|
&tStream,
|
||||||
// APP_CPU);
|
APP_CPU);
|
||||||
PRO_CPU);
|
// PRO_CPU);
|
||||||
|
|
||||||
// Pointers to the 2 frames, their respective sizes and index of the current frame
|
// Pointers to the 2 frames, their respective sizes and index of the current frame
|
||||||
char* fbs[2] = { NULL, NULL };
|
char* fbs[2] = { NULL, NULL };
|
||||||
|
@ -40,9 +45,18 @@ void camCB(void* pvParameters) {
|
||||||
//=== loop() section ===================
|
//=== loop() section ===================
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
captureAvg.initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
camera_fb_t* fb = NULL;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Grab a frame from the camera and query its size
|
// Grab a frame from the camera and query its size
|
||||||
camera_fb_t* fb = NULL;
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
uint32_t captureStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
fb = esp_camera_fb_get();
|
fb = esp_camera_fb_get();
|
||||||
size_t s = fb->len;
|
size_t s = fb->len;
|
||||||
|
@ -50,28 +64,29 @@ void camCB(void* pvParameters) {
|
||||||
// If frame size is more that we have previously allocated - request 125% of the current frame space
|
// If frame size is more that we have previously allocated - request 125% of the current frame space
|
||||||
if (s > fSize[ifb]) {
|
if (s > fSize[ifb]) {
|
||||||
fSize[ifb] = s * 4 / 3;
|
fSize[ifb] = s * 4 / 3;
|
||||||
fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]);
|
fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb], FAIL_IF_OOM, ANY_MEMORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy current frame into local buffer
|
// Copy current frame into local buffer
|
||||||
char* b = (char *)fb->buf;
|
char* b = (char *)fb->buf;
|
||||||
memcpy(fbs[ifb], b, s);
|
memcpy(fbs[ifb], b, s);
|
||||||
esp_camera_fb_return(fb);
|
esp_camera_fb_return(fb);
|
||||||
|
|
||||||
// Let other tasks run and wait until the end of the current frame rate interval (if any time left)
|
#if defined(BENCHMARK)
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
captureAvg.value(micros()-captureStart);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Only switch frames around if no frame is currently being streamed to a client
|
// Only switch frames around if no frame is currently being streamed to a client
|
||||||
// Wait on a semaphore until client operation completes
|
// Wait on a semaphore until client operation completes
|
||||||
xSemaphoreTake( frameSync, portMAX_DELAY );
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
|
|
||||||
// Do not allow interrupts while switching the current frame
|
// Do not allow interrupts while switching the current frame
|
||||||
taskENTER_CRITICAL(&xSemaphore);
|
// taskENTER_CRITICAL(&xSemaphore);
|
||||||
camBuf = fbs[ifb];
|
camBuf = fbs[ifb];
|
||||||
camSize = s;
|
camSize = s;
|
||||||
++ifb;
|
++ifb;
|
||||||
ifb = ifb & 1; // this should produce 1, 0, 1, 0, 1 ... sequence
|
ifb = ifb & 1; // this should produce 1, 0, 1, 0, 1 ... sequence
|
||||||
taskEXIT_CRITICAL(&xSemaphore);
|
// taskEXIT_CRITICAL(&xSemaphore);
|
||||||
|
|
||||||
// Let anyone waiting for a frame know that the frame is ready
|
// Let anyone waiting for a frame know that the frame is ready
|
||||||
xSemaphoreGive( frameSync );
|
xSemaphoreGive( frameSync );
|
||||||
|
@ -80,8 +95,9 @@ void camCB(void* pvParameters) {
|
||||||
// and it could start sending frames to the clients, if any
|
// and it could start sending frames to the clients, if any
|
||||||
xTaskNotifyGive( tStream );
|
xTaskNotifyGive( tStream );
|
||||||
|
|
||||||
// Immediately let other (streaming) tasks run
|
|
||||||
taskYIELD();
|
// Let other tasks run and wait until the end of the current frame rate interval (if any time left)
|
||||||
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
// If streaming task has suspended itself (no active clients to stream to)
|
// If streaming task has suspended itself (no active clients to stream to)
|
||||||
// there is no need to grab frames from the camera. We can save some juice
|
// there is no need to grab frames from the camera. We can save some juice
|
||||||
|
@ -89,6 +105,14 @@ void camCB(void* pvParameters) {
|
||||||
if ( eTaskGetState( tStream ) == eSuspended ) {
|
if ( eTaskGetState( tStream ) == eSuspended ) {
|
||||||
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
if ( millis() - lastPrintCam > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrintCam = millis();
|
||||||
|
Log.verbose("mjpegCB: average frame capture time: %d microseconds\n", captureAvg.currentValue() );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,13 +121,21 @@ void camCB(void* pvParameters) {
|
||||||
void handleJPGSstream(void)
|
void handleJPGSstream(void)
|
||||||
{
|
{
|
||||||
// Can only acommodate 10 clients. The limit is a default for WiFi connections
|
// Can only acommodate 10 clients. The limit is a default for WiFi connections
|
||||||
if ( !uxQueueSpacesAvailable(streamingClients) ) return;
|
if ( !uxQueueSpacesAvailable(streamingClients) ) {
|
||||||
|
Log.error("handleJPGSstream: Max number of WiFi clients reached\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new WiFi Client object to keep track of this one
|
// Create a new WiFi Client object to keep track of this one
|
||||||
WiFiClient* client = new WiFiClient();
|
WiFiClient* client = new WiFiClient();
|
||||||
|
if ( client == NULL ) {
|
||||||
|
Log.error("handleJPGSstream: Can not create new WiFi client - OOM\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
*client = server.client();
|
*client = server.client();
|
||||||
|
|
||||||
// Immediately send this client a header
|
// Immediately send this client a header
|
||||||
|
client->setTimeout(1);
|
||||||
client->write(HEADER, hdrLen);
|
client->write(HEADER, hdrLen);
|
||||||
client->write(BOUNDARY, bdrLen);
|
client->write(BOUNDARY, bdrLen);
|
||||||
|
|
||||||
|
@ -129,6 +161,19 @@ void streamCB(void * pvParameters) {
|
||||||
ulTaskNotifyTake( pdTRUE, /* Clear the notification value before exiting. */
|
ulTaskNotifyTake( pdTRUE, /* Clear the notification value before exiting. */
|
||||||
portMAX_DELAY ); /* Block indefinitely. */
|
portMAX_DELAY ); /* Block indefinitely. */
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
averageFilter<int32_t> streamAvg(10);
|
||||||
|
averageFilter<int32_t> waitAvg(10);
|
||||||
|
averageFilter<uint32_t> frameAvg(10);
|
||||||
|
averageFilter<float> fpsAvg(10);
|
||||||
|
uint32_t streamStart = 0;
|
||||||
|
streamAvg.initialize();
|
||||||
|
waitAvg.initialize();
|
||||||
|
frameAvg.initialize();
|
||||||
|
uint32_t lastPrint = millis();
|
||||||
|
uint32_t lastFrame = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Default assumption we are running according to the FPS
|
// Default assumption we are running according to the FPS
|
||||||
|
@ -157,21 +202,39 @@ void streamCB(void * pvParameters) {
|
||||||
// Ok. This is an actively connected client.
|
// Ok. This is an actively connected client.
|
||||||
// Let's grab a semaphore to prevent frame changes while we
|
// Let's grab a semaphore to prevent frame changes while we
|
||||||
// are serving this frame
|
// are serving this frame
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
xSemaphoreTake( frameSync, portMAX_DELAY );
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
|
|
||||||
client->write(CTNTTYPE, cntLen);
|
#if defined (BENCHMARK)
|
||||||
|
waitAvg.value(micros()-streamStart);
|
||||||
|
frameAvg.value(camSize);
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
sprintf(buf, "%d\r\n\r\n", camSize);
|
sprintf(buf, "%d\r\n\r\n", camSize);
|
||||||
|
client->flush();
|
||||||
|
client->write(CTNTTYPE, cntLen);
|
||||||
client->write(buf, strlen(buf));
|
client->write(buf, strlen(buf));
|
||||||
client->write((char*) camBuf, (size_t)camSize);
|
client->write((char*) camBuf, (size_t)camSize);
|
||||||
client->write(BOUNDARY, bdrLen);
|
client->write(BOUNDARY, bdrLen);
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
streamAvg.value(micros()-streamStart);
|
||||||
|
fpsAvg.value(1000.0 / (float) (millis()-lastFrame) );
|
||||||
|
lastFrame = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The frame has been served. Release the semaphore and let other tasks run.
|
||||||
|
// If there is a frame switch ready, it will happen now in between frames
|
||||||
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
// Since this client is still connected, push it to the end
|
// Since this client is still connected, push it to the end
|
||||||
// of the queue for further processing
|
// of the queue for further processing
|
||||||
xQueueSend(streamingClients, (void *) &client, 0);
|
xQueueSend(streamingClients, (void *) &client, 0);
|
||||||
|
|
||||||
// The frame has been served. Release the semaphore and let other tasks run.
|
|
||||||
// If there is a frame switch ready, it will happen now in between frames
|
|
||||||
xSemaphoreGive( frameSync );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +243,15 @@ void streamCB(void * pvParameters) {
|
||||||
vTaskSuspend(NULL);
|
vTaskSuspend(NULL);
|
||||||
}
|
}
|
||||||
// Let other tasks run after serving every client
|
// Let other tasks run after serving every client
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
if ( millis() - lastPrint > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrint = millis();
|
||||||
|
Log.verbose("streamCB: wait avg=%d, stream avg=%d us, frame avg size=%d bytes, fps=%S\n", waitAvg.currentValue(), streamAvg.currentValue(), frameAvg.currentValue(), String(fpsAvg.currentValue()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
volatile size_t camSize; // size of the current frame, byte
|
volatile size_t camSize; // size of the current frame, byte
|
||||||
volatile char* camBuf; // pointer to the current frame
|
volatile char* camBuf; // pointer to the current frame
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
#include <AverageFilter.h>
|
||||||
|
#define BENCHMARK_PRINT_INT 1000
|
||||||
|
averageFilter<uint32_t> captureAvg(10);
|
||||||
|
uint32_t lastPrintCam = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
void camCB(void* pvParameters) {
|
void camCB(void* pvParameters) {
|
||||||
|
|
||||||
|
@ -23,44 +29,57 @@ void camCB(void* pvParameters) {
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
size_t s = 0;
|
||||||
// Grab a frame from the camera and query its size
|
// Grab a frame from the camera and query its size
|
||||||
camera_fb_t* fb = NULL;
|
camera_fb_t* fb = NULL;
|
||||||
|
|
||||||
fb = esp_camera_fb_get();
|
#if defined(BENCHMARK)
|
||||||
size_t s = fb->len;
|
uint32_t benchmarkStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
// If frame size is more that we have previously allocated - request 125% of the current frame space
|
s = 0;
|
||||||
if (s > fSize[ifb]) {
|
fb = esp_camera_fb_get();
|
||||||
fSize[ifb] = s + s/4;
|
if ( fb ) {
|
||||||
fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]);
|
s = fb->len;
|
||||||
|
|
||||||
|
// If frame size is more that we have previously allocated - request 125% of the current frame space
|
||||||
|
if (s > fSize[ifb]) {
|
||||||
|
fSize[ifb] = s + s/4;
|
||||||
|
fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb], FAIL_IF_OOM, ANY_MEMORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy current frame into local buffer
|
||||||
|
char* b = (char *)fb->buf;
|
||||||
|
memcpy(fbs[ifb], b, s);
|
||||||
|
esp_camera_fb_return(fb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.error("camCB: error capturing image for frame %d\n", frameNumber);
|
||||||
|
vTaskDelay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy current frame into local buffer
|
#if defined(BENCHMARK)
|
||||||
char* b = (char *)fb->buf;
|
captureAvg.value(micros()-benchmarkStart);
|
||||||
memcpy(fbs[ifb], b, s);
|
#endif
|
||||||
esp_camera_fb_return(fb);
|
|
||||||
|
|
||||||
// Let other tasks run and wait until the end of the current frame rate interval (if any time left)
|
|
||||||
// taskYIELD();
|
|
||||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
|
||||||
|
|
||||||
// Only switch frames around if no frame is currently being streamed to a client
|
// Only switch frames around if no frame is currently being streamed to a client
|
||||||
// Wait on a semaphore until client operation completes
|
// Wait on a semaphore until client operation completes
|
||||||
// xSemaphoreTake( frameSync, portMAX_DELAY );
|
// xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
|
|
||||||
// Do not allow frame copying while switching the current frame
|
// Do not allow frame copying while switching the current frame
|
||||||
xSemaphoreTake( frameSync, xFrequency );
|
// if ( xSemaphoreTake( frameSync, xFrequency ) ) {
|
||||||
camBuf = fbs[ifb];
|
if ( xSemaphoreTake( frameSync, portMAX_DELAY ) ) {
|
||||||
camSize = s;
|
camBuf = fbs[ifb];
|
||||||
ifb++;
|
camSize = s;
|
||||||
ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence
|
ifb++;
|
||||||
frameNumber++;
|
ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence
|
||||||
// Let anyone waiting for a frame know that the frame is ready
|
frameNumber++;
|
||||||
xSemaphoreGive( frameSync );
|
// Let anyone waiting for a frame know that the frame is ready
|
||||||
|
xSemaphoreGive( frameSync );
|
||||||
|
}
|
||||||
|
|
||||||
// Immediately let other (streaming) tasks run
|
// Let other (streaming) tasks run
|
||||||
taskYIELD();
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
// If streaming task has suspended itself (no active clients to stream to)
|
// If streaming task has suspended itself (no active clients to stream to)
|
||||||
// there is no need to grab frames from the camera. We can save some juice
|
// there is no need to grab frames from the camera. We can save some juice
|
||||||
|
@ -72,6 +91,15 @@ void camCB(void* pvParameters) {
|
||||||
Log.verbose("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam));
|
Log.verbose("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam));
|
||||||
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
vTaskSuspend(NULL); // passing NULL means "suspend yourself"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
if ( millis() - lastPrintCam > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrintCam = millis();
|
||||||
|
Log.verbose("mjpegCB: average frame capture time: %d microseconds\n", captureAvg.currentValue() );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,8 +111,18 @@ void handleJPGSstream(void)
|
||||||
Log.verbose("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap());
|
Log.verbose("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap());
|
||||||
|
|
||||||
streamInfo_t* info = new streamInfo_t;
|
streamInfo_t* info = new streamInfo_t;
|
||||||
|
if ( info == NULL ) {
|
||||||
|
Log.error("handleJPGSstream: cannot allocate stream info - OOM\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WiFiClient* client = new WiFiClient();
|
WiFiClient* client = new WiFiClient();
|
||||||
|
if ( client == NULL ) {
|
||||||
|
Log.error("handleJPGSstream: cannot allocate WiFi client for streaming - OOM\n");
|
||||||
|
free(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
*client = server.client();
|
*client = server.client();
|
||||||
|
|
||||||
info->frame = frameNumber - 1;
|
info->frame = frameNumber - 1;
|
||||||
|
@ -138,37 +176,91 @@ void streamCB(void * pvParameters) {
|
||||||
info->client->write(HEADER, hdrLen);
|
info->client->write(HEADER, hdrLen);
|
||||||
info->client->write(BOUNDARY, bdrLen);
|
info->client->write(BOUNDARY, bdrLen);
|
||||||
|
|
||||||
|
#if defined(BENCHMARK)
|
||||||
|
averageFilter<int32_t> streamAvg(10);
|
||||||
|
averageFilter<int32_t> waitAvg(10);
|
||||||
|
averageFilter<uint32_t> frameAvg(10);
|
||||||
|
averageFilter<float> fpsAvg(10);
|
||||||
|
uint32_t streamStart = 0;
|
||||||
|
streamAvg.initialize();
|
||||||
|
waitAvg.initialize();
|
||||||
|
frameAvg.initialize();
|
||||||
|
fpsAvg.initialize();
|
||||||
|
uint32_t lastPrint = millis();
|
||||||
|
uint32_t lastFrame = millis();
|
||||||
|
#endif
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// Only bother to send anything if there is someone watching
|
// Only send anything if there is someone watching
|
||||||
if ( info->client->connected() ) {
|
if ( info->client->connected() ) {
|
||||||
|
|
||||||
if ( info->frame != frameNumber) {
|
if ( info->frame != frameNumber) { // do not send same frame twice
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
xSemaphoreTake( frameSync, portMAX_DELAY );
|
xSemaphoreTake( frameSync, portMAX_DELAY );
|
||||||
|
size_t currentSize = camSize;
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
waitAvg.value(micros()-streamStart);
|
||||||
|
frameAvg.value(currentSize);
|
||||||
|
streamStart = micros();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// ======================== OPTION1 ==================================
|
||||||
|
// server a copy of the buffer - overhead: have to allocate and copy a buffer for all frames
|
||||||
|
|
||||||
|
// /*
|
||||||
if ( info->buffer == NULL ) {
|
if ( info->buffer == NULL ) {
|
||||||
info->buffer = allocateMemory (info->buffer, camSize);
|
info->buffer = allocateMemory (info->buffer, camSize, FAIL_IF_OOM, ANY_MEMORY);
|
||||||
info->len = camSize;
|
info->len = camSize;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( camSize > info->len ) {
|
if ( camSize > info->len ) {
|
||||||
info->buffer = allocateMemory (info->buffer, camSize);
|
info->buffer = allocateMemory (info->buffer, camSize, FAIL_IF_OOM, ANY_MEMORY);
|
||||||
info->len = camSize;
|
info->len = camSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
memcpy(info->buffer, (const void*) camBuf, info->len);
|
memcpy(info->buffer, (const void*) camBuf, camSize);
|
||||||
|
|
||||||
xSemaphoreGive( frameSync );
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
info->frame = frameNumber;
|
sprintf(buf, "%d\r\n\r\n", currentSize);
|
||||||
|
info->client->flush();
|
||||||
info->client->write(CTNTTYPE, cntLen);
|
info->client->write(CTNTTYPE, cntLen);
|
||||||
sprintf(buf, "%d\r\n\r\n", info->len);
|
|
||||||
info->client->write(buf, strlen(buf));
|
info->client->write(buf, strlen(buf));
|
||||||
info->client->write((char*) info->buffer, (size_t)info->len);
|
info->client->write((char*) info->buffer, currentSize);
|
||||||
info->client->write(BOUNDARY, bdrLen);
|
info->client->write(BOUNDARY, bdrLen);
|
||||||
|
// */
|
||||||
|
|
||||||
|
// ======================== OPTION2 ==================================
|
||||||
|
// just server the comman buffer protected by mutex
|
||||||
|
/*
|
||||||
|
sprintf(buf, "%d\r\n\r\n", camSize);
|
||||||
|
info->client->write(CTNTTYPE, cntLen);
|
||||||
|
info->client->write(buf, strlen(buf));
|
||||||
|
info->client->write((char*) camBuf, (size_t)camSize);
|
||||||
|
|
||||||
|
xSemaphoreGive( frameSync );
|
||||||
|
|
||||||
|
info->client->write(BOUNDARY, bdrLen);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
info->frame = frameNumber;
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
streamAvg.value(micros()-streamStart);
|
||||||
|
fpsAvg.value(1000.0 / (float) (millis()-lastFrame) );
|
||||||
|
lastFrame = millis();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// client disconnected - clean up.
|
// client disconnected - clean up.
|
||||||
noActiveClients--;
|
noActiveClients--;
|
||||||
Log.trace("streamCB: Client disconnected\n");
|
|
||||||
Log.verbose("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task));
|
Log.verbose("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task));
|
||||||
info->client->stop();
|
info->client->stop();
|
||||||
if ( info->buffer ) {
|
if ( info->buffer ) {
|
||||||
|
@ -178,10 +270,19 @@ void streamCB(void * pvParameters) {
|
||||||
delete info->client;
|
delete info->client;
|
||||||
delete info;
|
delete info;
|
||||||
info = NULL;
|
info = NULL;
|
||||||
|
Log.trace("streamCB: Client disconnected\n");
|
||||||
|
vTaskDelay(100);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
// Let other tasks run after serving every client
|
// Let other tasks run after serving every client
|
||||||
if ( !xTaskDelayUntil(&xLastWakeTime, xFrequency) ) taskYIELD();
|
if ( xTaskDelayUntil(&xLastWakeTime, xFrequency) != pdTRUE ) taskYIELD();
|
||||||
|
|
||||||
|
#if defined (BENCHMARK)
|
||||||
|
if ( millis() - lastPrint > BENCHMARK_PRINT_INT ) {
|
||||||
|
lastPrint = millis();
|
||||||
|
Log.verbose("streamCB: wait avg=%d, stream avg=%d us, frame avg size=%d bytes, fps=%S\n", waitAvg.currentValue(), streamAvg.currentValue(), frameAvg.currentValue(), String(fpsAvg.currentValue()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue