290 lines
7.0 KiB
C++
290 lines
7.0 KiB
C++
/**
|
|
@file stream.cpp
|
|
|
|
@brief stream library.
|
|
|
|
Inspiration is from git repository https://gist.github.com/me-no-dev/d34fba51a8f059ac559bf62002e61aa3
|
|
|
|
@author Miroslav Pivovarsky
|
|
Contact: miroslav.pivovarsky@gmail.com
|
|
|
|
@bug: no know bug
|
|
*/
|
|
#include "WebStream.h"
|
|
|
|
#define PART_BOUNDARY "123456789000000000000987654321" ///< Must be unique for each stream
|
|
static const char *STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; ///< content type for stream
|
|
static const char *STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; ///< boundary for stream
|
|
static const char *STREAM_PART = "Content-Type: %s\r\nContent-Length: %u\r\n\r\n"; ///< part for stream
|
|
static const char *JPG_CONTENT_TYPE = "image/jpeg"; ///< content type for jpg
|
|
|
|
/**
|
|
* @brief Construct a new Async Buffer Response:: Async Buffer Response object
|
|
*
|
|
* @param buf
|
|
* @param len
|
|
* @param contentType
|
|
*/
|
|
AsyncBufferResponse::AsyncBufferResponse(uint8_t *buf, size_t len, const char *contentType) {
|
|
_buf = buf;
|
|
_len = len;
|
|
_callback = nullptr;
|
|
_code = 200;
|
|
_contentLength = _len;
|
|
_contentType = contentType;
|
|
_index = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Destroy the Async Buffer Response:: Async Buffer Response object
|
|
*
|
|
*/
|
|
AsyncBufferResponse::~AsyncBufferResponse() {
|
|
if (_buf != nullptr) {
|
|
free(_buf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if source is valid
|
|
*
|
|
* @return bool
|
|
*/
|
|
bool AsyncBufferResponse::_sourceValid() const {
|
|
return _buf != nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Fill buffer
|
|
*
|
|
* @param buf
|
|
* @param maxLen
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncBufferResponse::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
|
size_t ret = _content(buf, maxLen, _index);
|
|
if (ret != RESPONSE_TRY_AGAIN) {
|
|
_index += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Content
|
|
*
|
|
* @param buffer
|
|
* @param maxLen
|
|
* @param index
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncBufferResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) {
|
|
memcpy(buffer, _buf + index, maxLen);
|
|
if ((index + maxLen) == _len) {
|
|
free(_buf);
|
|
_buf = nullptr;
|
|
}
|
|
return maxLen;
|
|
}
|
|
|
|
/**
|
|
* @brief Construct a new Async Frame Response:: Async Frame Response object
|
|
*
|
|
* @param frame
|
|
* @param contentType
|
|
*/
|
|
AsyncFrameResponse::AsyncFrameResponse(camera_fb_t *frame, const char *contentType) {
|
|
_callback = nullptr;
|
|
_code = 200;
|
|
_contentLength = frame->len;
|
|
_contentType = contentType;
|
|
_index = 0;
|
|
fb = frame;
|
|
}
|
|
|
|
/**
|
|
* @brief Destroy the Async Frame Response:: Async Frame Response object
|
|
*
|
|
*/
|
|
AsyncFrameResponse::~AsyncFrameResponse() {
|
|
if (fb != nullptr) {
|
|
esp_camera_fb_return(fb);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if source is valid
|
|
*
|
|
* @return bool
|
|
*/
|
|
bool AsyncFrameResponse::_sourceValid() const {
|
|
return fb != nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Fill buffer
|
|
*
|
|
* @param buf
|
|
* @param maxLen
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncFrameResponse::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
|
size_t ret = _content(buf, maxLen, _index);
|
|
if (ret != RESPONSE_TRY_AGAIN) {
|
|
_index += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Content
|
|
*
|
|
* @param buffer
|
|
* @param maxLen
|
|
* @param index
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncFrameResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) {
|
|
memcpy(buffer, fb->buf + index, maxLen);
|
|
if ((index + maxLen) == fb->len) {
|
|
esp_camera_fb_return(fb);
|
|
fb = nullptr;
|
|
}
|
|
return maxLen;
|
|
}
|
|
|
|
/**
|
|
* @brief Construct a new Async Jpeg Stream Response:: Async Jpeg Stream Response object
|
|
*
|
|
* @param i_cam
|
|
* @param i_log
|
|
*/
|
|
AsyncJpegStreamResponse::AsyncJpegStreamResponse(Camera *i_cam, Logs *i_log) {
|
|
_callback = nullptr;
|
|
_code = 200;
|
|
_contentLength = 0;
|
|
_contentType = STREAM_CONTENT_TYPE;
|
|
_sendContentLength = false;
|
|
_chunked = true;
|
|
_index = 0;
|
|
lastAsyncRequest = 0;
|
|
memset(&_frame, 0, sizeof(camera_frame_t));
|
|
camera = i_cam;
|
|
log = i_log;
|
|
camera->SetStreamStatus(true);
|
|
}
|
|
|
|
/**
|
|
* @brief Destroy the Async Jpeg Stream Response:: Async Jpeg Stream Response object
|
|
*
|
|
*/
|
|
AsyncJpegStreamResponse::~AsyncJpegStreamResponse() {
|
|
camera->SetStreamStatus(false);
|
|
camera->CaptureReturnFrameBuffer();
|
|
}
|
|
|
|
/**
|
|
* @brief Check if source is valid
|
|
*
|
|
* @return bool
|
|
*/
|
|
bool AsyncJpegStreamResponse::_sourceValid() const {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Fill buffer
|
|
*
|
|
* @param buf
|
|
* @param maxLen
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncJpegStreamResponse::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
|
size_t ret = _content(buf, maxLen, _index);
|
|
if (ret != RESPONSE_TRY_AGAIN) {
|
|
_index += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Content - get picture from camera and send it to client
|
|
*
|
|
* @param buffer
|
|
* @param maxLen
|
|
* @param index
|
|
* @return size_t
|
|
*/
|
|
size_t AsyncJpegStreamResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) {
|
|
|
|
if (!_frame.fb || _frame.index == _frame.fb->len) {
|
|
delay(1);
|
|
|
|
if (index && _frame.fb) {
|
|
uint64_t end = (uint64_t)micros();
|
|
int fp = (end - lastAsyncRequest) / 1000;
|
|
float fps = 1000.0 / fp;
|
|
char buf[50] = { '\0' };
|
|
camera->StreamSetFrameSize(_frame.fb->len / 1024);
|
|
camera->StreamSetFrameFps(fps);
|
|
////sprintf(buf, "Size: %uKB, Time: %ums (%.1f fps)", _frame.fb->len / 1024, fp, fps);
|
|
sprintf(buf, "Size: %uKB, FPS: %.1f", _frame.fb->len / 1024, fps);
|
|
Serial.println(buf);
|
|
lastAsyncRequest = end;
|
|
camera->CaptureReturnFrameBuffer();
|
|
_frame.fb = NULL;
|
|
}
|
|
|
|
/* check space for headers */
|
|
if (maxLen < (strlen(STREAM_BOUNDARY) + strlen(STREAM_PART) + strlen(JPG_CONTENT_TYPE) + 8)) {
|
|
log->AddEvent(LogLevel_Error, F("Stream Not space for headers"));
|
|
return RESPONSE_TRY_AGAIN;
|
|
}
|
|
|
|
/* get frame */
|
|
_frame.index = 0;
|
|
camera->CaptureStream(&_dframe);
|
|
_frame.fb = &_dframe;
|
|
|
|
if (_frame.fb == NULL) {
|
|
log->AddEvent(LogLevel_Error, F("Stream capture frame failed"));
|
|
return 0;
|
|
}
|
|
|
|
/* send boundary */
|
|
size_t blen = 0;
|
|
if (index) {
|
|
blen = strlen(STREAM_BOUNDARY);
|
|
memcpy(buffer, STREAM_BOUNDARY, blen);
|
|
buffer += blen;
|
|
}
|
|
|
|
/* send header */
|
|
size_t hlen = sprintf((char *)buffer, STREAM_PART, JPG_CONTENT_TYPE, _frame.fb->len);
|
|
buffer += hlen;
|
|
|
|
/* send frame */
|
|
hlen = maxLen - hlen - blen;
|
|
if (hlen > _frame.fb->len) {
|
|
maxLen -= hlen - _frame.fb->len;
|
|
hlen = _frame.fb->len;
|
|
}
|
|
|
|
memcpy(buffer, _frame.fb->buf, hlen);
|
|
_frame.index += hlen;
|
|
delay(1);
|
|
return maxLen;
|
|
}
|
|
|
|
/* check next send data */
|
|
size_t available = _frame.fb->len - _frame.index;
|
|
if (maxLen > available) {
|
|
maxLen = available;
|
|
}
|
|
|
|
delay(1);
|
|
memcpy(buffer, _frame.fb->buf + _frame.index, maxLen);
|
|
_frame.index += maxLen;
|
|
return maxLen;
|
|
}
|
|
|
|
/* EOF */ |