Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
545186a
Abstracted some complexity related to WebcamStream.
slav-at-attachix Feb 6, 2026
3da840e
Minor fixes.
slav-at-attachix Feb 9, 2026
8e62c54
Implemented review recommendations.
slav-at-attachix Feb 9, 2026
8a9e03a
Fix an issue with Firefox not showing the images.
slav-at-attachix Feb 10, 2026
8c67742
Every consumer gets its own camera stream. We still have timer to tak…
slav-at-attachix Feb 11, 2026
cb67514
These changes should make the process less CPU intensive.
slav-at-attachix Feb 11, 2026
dbd74ad
Push Tcp content even when the source is not ready yet.
slav-at-attachix Feb 12, 2026
b323c42
Configure WiFi
mikee47 Feb 12, 2026
5f605d5
Use Periodic timer for frame sync
mikee47 Feb 12, 2026
ec64838
Avoid modifying MultiStream?
mikee47 Feb 12, 2026
8ae7829
Abstracted some complexity related to WebcamStream.
slav-at-attachix Feb 6, 2026
d218844
Minor fixes.
slav-at-attachix Feb 9, 2026
2152dbd
Implemented review recommendations.
slav-at-attachix Feb 9, 2026
5cd49d3
Fix an issue with Firefox not showing the images.
slav-at-attachix Feb 10, 2026
b610560
Every consumer gets its own camera stream. We still have timer to tak…
slav-at-attachix Feb 11, 2026
336036a
These changes should make the process less CPU intensive.
slav-at-attachix Feb 11, 2026
f3b428b
Push Tcp content even when the source is not ready yet.
slav-at-attachix Feb 12, 2026
bd7fc6b
Merge remote-tracking branch 'mikee47/feature/webcam-fps' into featur…
slav-at-attachix Feb 19, 2026
0a1dbd1
Avoid modifying MultiStream?
mikee47 Feb 12, 2026
34dd43c
Enable FPS by default.
slav-at-attachix Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sming/Components/Network/src/Network/TcpConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ int TcpConnection::write(IDataSourceStream* stream)
char buffer[NETWORK_SEND_BUFFER_SIZE];
auto bytesRead = stream->readMemoryBlock(buffer, std::min(sizeof(buffer), available));
if(bytesRead == 0) {
if(pushCount == 0 && stream->available() != 0) {
pushCount = 1; // the stream has data but it is not ready yet, wait for next poll to try again
}
break;
}

Expand Down
17 changes: 6 additions & 11 deletions Sming/Libraries/WebCam/src/Camera/CameraInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CameraInterface
}

/**
* Prepare for the next picture
* @brief Prepare for the next picture
*/
virtual void next()
{
Expand All @@ -60,19 +60,14 @@ class CameraInterface
virtual bool capture() = 0;

/**
* Gets the size of the current picture
* @brief Gets the frames per second the camera can capture
*/
virtual size_t getSize() = 0;
virtual uint8_t getFramesPerSecond() = 0;

/**
* @brief Read picture data from the camera.
* @param buffer the allocated data buffer to store the data
* @param size the size of the allocated buffer
* @param offset
*
* @retval bytes successfully read and stored in the buffer
/** @brief Create a new image stream. The stream is owned by the caller and must be deleted.
* @retval IDataSourceStream Pointer to the new image stream
*/
virtual size_t read(char* buffer, size_t size, size_t offset = 0) = 0;
virtual IDataSourceStream* newImageStream() = 0;

protected:
CameraState state = eWCS_NOT_READY;
Expand Down
47 changes: 19 additions & 28 deletions Sming/Libraries/WebCam/src/Camera/FakeCamera.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
#include "CameraInterface.h"
#include <Data/Stream/FileStream.h>

class FakeCamera: public CameraInterface
class FakeCamera : public CameraInterface
{
public:

/**
* Sets the list of all images that should be used to roll over.
* @brief Sets the list of all images that should be used to roll over.
*/
FakeCamera(const Vector<String>& files)
FakeCamera()
{
}

void addImage(const String& filename)
{
this->files = files;
files.add(filename);
}

const String getMimeType() const override
Expand All @@ -43,8 +46,6 @@ class FakeCamera: public CameraInterface
return true;
}

state = eWCS_INITIALISING;
spiffs_mount(); // Mount file system, in order to work with files
state = eWCS_READY;
index = 0;
return true;
Expand All @@ -59,40 +60,30 @@ class FakeCamera: public CameraInterface
return false;
}

if(files.count() == 0) {
debug_w("No images added to the fake camera");
return false;
}

// go to the next picture.
auto& filename = files[index++];
index++;
if(index == files.count()) {
index = 0;
}
if(!file.open(filename)) {
return false;
}

state = eWCS_HAS_PICTURE;

return true;
}

/**
* Gets the size of the current picture
*/
size_t getSize() override
uint8_t getFramesPerSecond() override
{
return file.getSize();
return 10; // fake camera supports 10 fps
}

/**
* @brief Read picture data from the camera.
* @param buffer the allocated data buffer to store the data
* @param size the size of the allocated buffer
* @param offset
*
* @retval bytes successfully read and stored in the buffer
*/
size_t read(char* buffer, size_t size, size_t offset = 0) override
IDataSourceStream* newImageStream() override
{
// get the current picture and read the desired data from it.
file.seekFrom(offset, SeekOrigin::Start);
return file.readMemoryBlock(buffer, size);
return new FileStream(files[index]);
}

private:
Expand Down
83 changes: 38 additions & 45 deletions Sming/Libraries/WebCam/src/WebcamStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,61 @@

#pragma once

#include <Data/Stream/DataSourceStream.h>
#include <Data/Stream/MultipartStream.h>
#include "Camera/CameraInterface.h"
#include <Data/Range.h>

class WebcamStream: public IDataSourceStream
/**
* @brief Webcam stream producer for multipart streaming
*/
class WebcamStream : public MultipartStream
{
public:
/**
* @param camera pointer to the camera object.
*/
WebcamStream(CameraInterface* camera, size_t blockSize = 512): camera(camera), blockSize(blockSize)
WebcamStream(CameraInterface& camera) : MultipartStream(std::bind(&WebcamStream::produce, this)), camera(camera)
{
assert(camera != nullptr);
}

uint16_t readMemoryBlock(char* data, int bufSize)
MultipartStream::BodyPart produce()
{
if(camera->getState() == eWCS_HAS_PICTURE) {
if(!size) {
size = camera->getSize();
offset = 0;
}
MultipartStream::BodyPart result;

return camera->read(data, bufSize, offset);
}
camera.capture();
result.stream = camera.newImageStream();

if(camera->getState() != eWCS_WORKING) {
if(camera->capture()) {
size = 0;
offset = 0;
}
}
result.headers = new HttpHeaders();
(*result.headers)[HTTP_HEADER_CONTENT_TYPE] = camera.getMimeType();
(*result.headers)[HTTP_HEADER_CONTENT_LENGTH] = result.stream->available();

return 0;
}

int available()
{
return size;
}
// Don't count first image
if(started) {
newImage = true;
} else {
// Note: This won't take into account changes of camera frame rate
int fps = TRange(1, 100).clip(camera.getFramesPerSecond());
frameTimer.reset(1000 / fps);
started = true;
}

bool seek(int len)
{
offset += len; // TODO: check for invalid offsets
return true;
return result;
}

bool isFinished()
uint16_t readMemoryBlock(char* data, int bufSize) override
{
bool finished = (size && offset >= size);
if(finished) {
camera->next();
if(newImage) {
if(!frameTimer.expired()) {
debug_d("Defer read to maintain fps");
return 0;
}
newImage = false;
frameTimer.start();
}
return finished;
}

~WebcamStream()
{
camera = nullptr;
return MultipartStream::readMemoryBlock(data, bufSize);
}

private:
CameraInterface* camera = nullptr;
size_t blockSize;
size_t size = 0;
size_t offset = 0;
CameraInterface& camera;
OneShotFastMs frameTimer;
bool started{false};
bool newImage{false};
};
28 changes: 8 additions & 20 deletions samples/WebcamServer/app/application.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <SmingCore.h>

#include <Data/Stream/MultipartStream.h>
#include <WebcamStream.h>
#include <Camera/FakeCamera.h>

Expand All @@ -13,7 +12,7 @@
namespace
{
HttpServer server;
FakeCamera* camera;
FakeCamera camera;

/*
* default http handler to check if server is up and running
Expand All @@ -36,24 +35,11 @@ void onFile(HttpRequest& request, HttpResponse& response)
}
}

MultipartStream::BodyPart snapshotProducer()
{
MultipartStream::BodyPart result;

WebcamStream* webcamStream = new WebcamStream(camera);
result.stream = webcamStream;

result.headers = new HttpHeaders();
(*result.headers)[HTTP_HEADER_CONTENT_TYPE] = camera->getMimeType();

return result;
}

void onStream(HttpRequest& request, HttpResponse& response)
{
Serial.println(_F("perform onCapture()"));

MultipartStream* stream = new MultipartStream(snapshotProducer);
WebcamStream* stream = new WebcamStream(camera);
response.sendDataStream(stream, F("multipart/x-mixed-replace; boundary=") + stream->getBoundary());
}

Expand All @@ -66,15 +52,15 @@ void onFavicon(HttpRequest& request, HttpResponse& response)
void startWebServer()
{
// Initialize the camera
Vector<String> images;
for(unsigned int i = 1; i < 6; i++) {
String s = "img";
s.concat(i, DEC, 2);
s += ".jpeg";
images.add(s);
camera.addImage(s);
}
camera = new FakeCamera(images);
camera->init();

spiffs_mount(); // Mount file system, in order to work with files
camera.init();

// .. and run the HTTP server
server.listen(80);
Expand All @@ -94,6 +80,8 @@ void init()
spiffs_mount();

WifiStation.enable(true);
WifiStation.config(WIFI_SSID, WIFI_PWD);
WifiAccessPoint.enable(false);

System.onReady(startWebServer);
}
Loading