From 168c5a831f58554a93bb516a3402ab60e14a6d57 Mon Sep 17 00:00:00 2001 From: Steffen Pohle Date: Wed, 7 Jan 2026 00:07:25 +0100 Subject: [PATCH] first frame is visible --- Makefile | 8 +- configuration.cc | 81 +++++++- configuration.h | 28 ++- convert.h | 4 +- inmemoryfile.cc | 57 ++++++ inmemoryfile.h | 22 ++ main.cc | 84 +++++++- miniwebcam.h | 10 + video.cc | 518 ++++++++++++++++++++++++++++++++++++++++++----- video.h | 93 ++++++--- videoframe.cc | 144 +++++++++++++ videoframe.h | 38 ++++ webserver.cc | 57 +++++- webserver.h | 6 - 14 files changed, 1042 insertions(+), 108 deletions(-) create mode 100644 inmemoryfile.cc create mode 100644 inmemoryfile.h create mode 100644 videoframe.cc create mode 100644 videoframe.h delete mode 100644 webserver.h diff --git a/Makefile b/Makefile index 7da3183..d5b30aa 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,7 @@ DEFAULT_SERVERPORT=20010 CXX=g++ CXXFLAGS= -ggdb -fPIC -Wall -std=c++11 -I/usr/local/include LDFLAGS= -lUDPTCPNetwork -L/usr/local/lib -ljpeg -OBJFILES= webserver.o configuration.o main.o video.o convert.o debayer.o - +OBJFILES= webserver.o configuration.o main.o video.o convert.o debayer.o inmemoryfile.o videoframe.o all: dep miniwebcam miniwebcam: dep $(OBJFILES) @@ -45,6 +44,9 @@ rebuild: clean all dep: $(CXX) -MM `ls *.cc` $(CXXFLAGS) > $(DEPENDFILE) +keygen: + openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout ssl-key.pem -out ssl-cert.pem + clean: rm *.s -rf rm *.o -rf @@ -56,6 +58,8 @@ clean: rm -rf miniwebcam rm -rf config.h rm -rf Makefile.rules + rm -rf ssl-key.pem + rm -rf ssl-cert.pem cleanall: clean diff --git a/configuration.cc b/configuration.cc index cf3fbbe..7db2cb5 100644 --- a/configuration.cc +++ b/configuration.cc @@ -18,8 +18,15 @@ Configuration::Configuration() { https_port = DEFAULT_HTTPS_PORT; filename = DEFAULT_CONFIG_FILE; runasdaemon = 0; - + vdev_iomode = DEFAULT_VDEV_IOMODE; + vdev_device = DEFAULT_VDEV_DEVICE; + ssl_key = DEFAULT_SSL_KEY; + ssl_cert = DEFAULT_SSL_CERT; initflags = 0; + vdev_height = DEFAULT_VDEV_HEIGHT; + vdev_width = DEFAULT_VDEV_WIDTH; + web_height = DEFAULT_WEB_HEIGHT; + web_width = DEFAULT_WEB_WIDTH; }; Configuration::~Configuration() { @@ -39,6 +46,14 @@ int Configuration::PrintConfig() { jp.AddObject("http_port", http_port); jp.AddObject("https_port", https_port); + jp.AddObject("ssl-key-file", ssl_key); + jp.AddObject("ssl-cert-file", ssl_cert); + jp.AddObject("vdev-iomode", vdev_iomode); + jp.AddObject("vdev-device", vdev_device); + jp.AddObject("vdev-height", vdev_height); + jp.AddObject("vdev-width", vdev_width); + jp.AddObject("web-height", web_height); + jp.AddObject("web-width", web_width); // // output the json string @@ -55,6 +70,7 @@ int Configuration::PrintConfig() { int Configuration::LoadFile(std::string fn) { JSONParse jp; int i; + std::string s; // // save current language and set to default C @@ -69,6 +85,15 @@ int Configuration::LoadFile(std::string fn) { if (jp.GetValueInt("http_port", &i)) http_port = i; if (jp.GetValueInt("https_port", &i)) https_port = i; + if (jp.GetValueString("ssl-key-file", &s)) ssl_key = s; + if (jp.GetValueString("ssl-cert-file", &s)) ssl_cert = s; + if (jp.GetValueInt("web-height", &i)) web_height = i; + if (jp.GetValueInt("web-width", &i)) web_width = i; + + if (jp.GetValueString("vdev-device", &s)) vdev_device = s; + if (jp.GetValueInt("vdev-iomode", &i)) vdev_iomode = i; + if (jp.GetValueInt("vdev-height", &i)) vdev_height = i; + if (jp.GetValueInt("vdev-width", &i)) vdev_width = i; // // restore language @@ -92,6 +117,7 @@ int Configuration::LoadArgs(int argc, char **argv) { ErrorExit("config file missing", -1); } if (strcmp(argv[i], "-h") == 0) initflags |= CONF_INITFLAGS_HELP; + if (strcmp(argv[i], "--help") == 0) initflags |= CONF_INITFLAGS_HELP; if (strcmp(argv[i], "-http_port") == 0) { if (++i < argc) { @@ -107,6 +133,51 @@ int Configuration::LoadArgs(int argc, char **argv) { else ErrorExit("missing port parameter", -1); } + if (strcmp(argv[i], "-websize") == 0) { + if ((i=i+2) < argc) { + web_width = atoi(argv[i-1]); + web_height = atoi(argv[i]); + } + else + ErrorExit("missing web resolution parameter", -1); + } + + if (strcmp(argv[i], "-vdevsize") == 0) { + if ((i=i+2) < argc) { + vdev_width = atoi(argv[i-1]); + vdev_height = atoi(argv[i]); + } + else + ErrorExit("missing video device resolution parameter", -1); + } + if (strcmp(argv[i], "-vdeviomode") == 0) { + if (++i < argc) { + vdev_iomode = atoi(argv[i]); + } + else + ErrorExit("missing iomode parameter", -1); + } + if (strcmp(argv[i], "-vdevdevice") == 0) { + if (++i < argc) { + vdev_device = argv[i]; + } + else + ErrorExit("missing device parameter", -1); + } + if (strcmp(argv[i], "-sslkey") == 0) { + if (++i < argc) { + ssl_key = argv[i]; + } + else + ErrorExit("missing sslkey file", -1); + } + if (strcmp(argv[i], "-sslcert") == 0) { + if (++i < argc) { + ssl_cert = argv[i]; + } + else + ErrorExit("missing sslcertfile", -1); + } } return 1; @@ -120,6 +191,14 @@ void Configuration::Help() { printf ("\n"); printf (" -http_port INT port to listen for http connections\n"); printf (" -https_port INT port to listen for https connections\n"); + printf (" -sslkey FILE ssl key file\n"); + printf (" -sslcert FILE ssl certfile\n"); + printf ("\n"); + printf (" -websize INT INT define the web output resolution\n"); + printf ("\n"); + printf (" -vdeviomode INT IOMode to read the video data, 0-read, 1-MMap\n"); + printf (" -vdevdevice FILE Device File i.e. /dev/video2\n"); + printf (" -vdevsize INT INT define video input resolution\n"); printf ("\n"); printf (" -config FILE load this configfile\n"); printf (" -dump_config print the config file\n"); diff --git a/configuration.h b/configuration.h index a240314..c08e5f7 100644 --- a/configuration.h +++ b/configuration.h @@ -8,22 +8,40 @@ #include "config.h" -#define DEFAULT_HTTP_PORT 10080 -#define DEFAULT_HTTPS_PORT 10081 -#define DEFAULT_CONFIG_FILE "/etc/miniwebcam.conf" +#define DEFAULT_HTTP_PORT 8080 +#define DEFAULT_HTTPS_PORT 8081 +#define DEFAULT_CONFIG_FILE "/etc/miniwebcam/miniwebcam.conf" +#define DEFAULT_SSL_KEY "/etc/miniwebcam/ssl-key.pem" +#define DEFAULT_SSL_CERT "/etc/miniwebcam/ssl-cert.pem" +#define DEFAULT_VDEV_IOMODE 1 +#define DEFAULT_VDEV_DEVICE "/dev/video0" +#define DEFAULT_VDEV_HEIGHT -1 +#define DEFAULT_VDEV_WIDTH -1 +#define DEFAULT_WEB_HEIGHT 1024 +#define DEFAULT_WEB_WIDTH 786 #define CONF_INITFLAGS_PRINT 0x0001 #define CONF_INITFLAGS_HELP 0x0002 class Configuration { private: +public: int http_port; int https_port; + std::string ssl_key; + std::string ssl_cert; int runasdaemon; std::string filename; int initflags; -public: + int web_height; + int web_width; + + int vdev_height; + int vdev_width; + int vdev_iomode; + std::string vdev_device; + Configuration(); ~Configuration(); @@ -35,8 +53,6 @@ public: int PrintConfig(); // print current configuration void Help(); // print Help - // - }; extern Configuration config; diff --git a/convert.h b/convert.h index 85e0af0..f1bb80d 100644 --- a/convert.h +++ b/convert.h @@ -7,8 +7,7 @@ #include #include #include - -#include "video.h" +#include "videoframe.h" #ifndef CLEAR #define CLEAR(x) memset (&(x), 0, sizeof (x)) @@ -18,6 +17,7 @@ struct { struct jpeg_decompress_struct cinfo; } typedef ConvertData; + int Convert (ConvertData *cdata, VideoFrame *dest, unsigned char *ptrsrc, int srcsize, uint32_t pixelformat, int srcw, int srch); int ConvertStart(ConvertData *cdata, uint32_t pixelformat); int ConvertStop(ConvertData *cdata, uint32_t pixelformat); diff --git a/inmemoryfile.cc b/inmemoryfile.cc new file mode 100644 index 0000000..cc6bb4c --- /dev/null +++ b/inmemoryfile.cc @@ -0,0 +1,57 @@ + +#include +#include +#include "inmemoryfile.h" + +InMemoryFile::InMemoryFile() { + mem = malloc (INMEMORYFILE_ALLOCATEBLOCK); + memallocsize = INMEMORYFILE_ALLOCATEBLOCK; + memsize = 0; +}; + +InMemoryFile::~InMemoryFile() { + if (mem != NULL) free (mem); + mem = NULL; + memallocsize = 0; + memsize = 0; +}; + + +int InMemoryFile::Allocate(size_t newsize) { + // shrink size only if new memory block is smaller then 25% + if ((newsize < memallocsize / 4) || (newsize > memallocsize)) { + memallocsize = (1+(newsize/INMEMORYFILE_ALLOCATEBLOCK))*INMEMORYFILE_ALLOCATEBLOCK; + memsize = newsize; + mem = realloc(mem, memallocsize); + if (mem == NULL || memsize > memallocsize) { + debug ("could not reallocate memory memsize:%d memallocsize:%d\n.", memsize, memallocsize); + exit (1); + } + } + return 1; +}; + + +InMemoryFile InMemoryFile::operator=(InMemoryFile rightside) { + memsize = rightside.memsize; + memallocsize = 1+(memsize/INMEMORYFILE_ALLOCATEBLOCK)*INMEMORYFILE_ALLOCATEBLOCK; + mem = realloc(mem, memallocsize+1); + if (mem == NULL || memsize > memallocsize) { + debug ("could not reallocate memory: %s\n.", strerror(errno)); + exit (1); + } + memcpy (mem, rightside.mem, memsize); + + return *this; +}; + + +/// @brief copy the memory. +/// @param srcptr pointer to the source memory +/// @param srcsize size of the memory +/// @return 1 on success, 0 on error +int InMemoryFile::CopyFrom(void *srcptr, size_t srcsize) { + if (Allocate (srcsize) == 0) return 0; + memcpy (mem, srcptr, memsize); + return 1; +} diff --git a/inmemoryfile.h b/inmemoryfile.h new file mode 100644 index 0000000..9243711 --- /dev/null +++ b/inmemoryfile.h @@ -0,0 +1,22 @@ +#ifndef _INMEMORYFILE_H_ +#define _INMEMORYFILE_H_ + + +#define INMEMORYFILE_ALLOCATEBLOCK 4096 +class InMemoryFile { + private: + size_t memallocsize; + public: + void *mem; + size_t memsize; + + InMemoryFile operator=(InMemoryFile rightside); + + InMemoryFile(); + ~InMemoryFile(); + + int Allocate(size_t newsize); + int CopyFrom(void *srcptr, size_t srcsize); +}; + +#endif diff --git a/main.cc b/main.cc index f8c60f6..a7721eb 100644 --- a/main.cc +++ b/main.cc @@ -1,18 +1,30 @@ #include #include +#include +#include #include #include "configuration.h" #include "miniwebcam.h" +int running = 1; + +static void sig_int(int); +int SetupSignals(); +VideoFrame currentimage; + void ErrorExit(std::string text, int errorcode) { printf ("Error: %s\n", text.c_str()); exit (errorcode); }; + int main(int argc, char **argv) { + printf ("MiniWebCam:\n"); + if (SetupSignals() == 0) return 0; + config.LoadArgs (argc, argv); config.LoadFile (config.GetFilename()); if (config.GetInitFlags() & CONF_INITFLAGS_PRINT) { @@ -25,16 +37,74 @@ int main(int argc, char **argv) { return 0; } - printf ("MiniWebCam:\n"); - VideoFrame v; - VideoFrameFloat vf; + VideoDevice_V4L2 vdev; + WebCamServer webserver; + currentimage.TestScreen(1920, 1080); + + webserver.SetupPorts(config.http_port, config.https_port); + webserver.SetupSSL(config.ssl_key, config.ssl_cert); + webserver.Start(); + + if (vdev.SetDevice (config.vdev_device, config.vdev_width, config.vdev_height) == 0) { + debug ("could not setup device.\n"); + return 0; + } + + if (vdev.SetIOMode (config.vdev_iomode) == 0) { + debug ("could not setup iomode.\n"); + return 0; + } + + if (vdev.Start() == 0) { + debug ("Error: could not start video.\n"); + return 0; + } + - printf ("VideoFrame Size:\n"); - v.SetSize(100,100); - printf ("VideoFrameFloat Size:\n"); - vf.SetSize(100,100); + while (running) { + if (vdev.GetFrame(¤timage) == 0) { + vdev.Stop(); + vdev.Start(); + } + + webserver.Loop(); + usleep (1000); + } + + vdev.Stop(); + webserver.Stop(); return 0; }; + +int SetupSignals() { + if (signal(SIGINT, sig_int) == SIG_ERR) { + errorexit ("%s:%d could not set signal for SIGINT\n", __FILE__, __LINE__); + return 0; + } + if (signal(SIGTERM, sig_int) == SIG_ERR) { + errorexit ("%s:%d could not set signal for SIGTERM\n", __FILE__, __LINE__); + return 0; + } + if (signal(SIGHUP, sig_int) == SIG_ERR) { + errorexit ("%s:%d could not set signal for SIGHUB\n", __FILE__, __LINE__); + return 0; + } + if (signal(SIGUSR1, sig_int) == SIG_ERR) { + errorexit ("%s:%d could not set signal for SIGHUB\n", __FILE__, __LINE__); + return 0; + } + return 1; +}; + +static void sig_int(int sig) { + if (signal (SIGINT, sig_int) == SIG_ERR) errorexit ("could not set up signal SIGINT\n"); + + printf ("\n\nSignal Int\n\n"); + + running = 0; +} + + diff --git a/miniwebcam.h b/miniwebcam.h index e39a42f..84d7da5 100644 --- a/miniwebcam.h +++ b/miniwebcam.h @@ -9,9 +9,19 @@ #include "configuration.h" #include "miniwebcam.h" #include "video.h" +#include "inmemoryfile.h" + +class WebCamServer : public WebServer { + private: + protected: + public: + int HandleRequest (WebRequestBuffer *requestbuffer, WebServerClient *webclient); +}; void ErrorExit(std::string text, int errorcode); +extern VideoFrame currentimage; + #endif diff --git a/video.cc b/video.cc index 64626e3..183b84d 100644 --- a/video.cc +++ b/video.cc @@ -1,108 +1,520 @@ #include "miniwebcam.h" #include "video.h" +#include "convert.h" #include #include -VideoFrame::VideoFrame() { - mem = NULL; - width = 0; - height = 0; - mem_allocated = 0; + + +VideoDevice::VideoDevice() { }; -VideoFrame::~VideoFrame() { - FreeFrame(); + +VideoDevice::~VideoDevice() { + }; -void VideoFrame::FreeFrame() { - if (mem != NULL) { - free (mem); - mem = NULL; - width = 0; - height = 0; - mem_allocated = 0; - } +VideoDevice_V4L2::VideoDevice_V4L2() { + conf_height = 0; + conf_width = 0; + conf_videodev = ""; + conf_iomode = 0; + fd = -1; }; -void VideoFrame::AllocateFrame() { - printf ("VideoFrame::AllocateFrame()\n"); - int memnewsize = width * height * 3; - if (memnewsize >= mem_allocated) return; - else if (memnewsize == 0) FreeFrame(); +VideoDevice_V4L2::~VideoDevice_V4L2() { + if (fd != 0) Stop(); +}; + + + +int VideoDevice_V4L2::SetDevice(std::string newdevice, int nwidth, int nheight) { + int isrunning = 0; + + if (fd != -1) { + Stop(); + isrunning = 1; + } - mem = (unsigned char *) realloc (mem, memnewsize); - mem_allocated = memnewsize; + conf_videodev = newdevice; + conf_width = nwidth; + conf_height = nheight; - if (mem == NULL) { - debug ("Error on allocation new frame\n"); - exit (1); + if (isrunning) { + if (Start() == 0) return 0; } + + return 1; }; -int VideoFrame::SetSize(int w, int h) { - if (w < 0 && h < 0) return 0; +int VideoDevice_V4L2::SetIOMode(int newiomode) { + int isrunning = 0; - width = w; - height = h; + if (fd != -1) { + Stop(); + isrunning = 1; + } - AllocateFrame(); + conf_iomode = newiomode; + + if (isrunning) { + if (Start() == 0) return 0; + } return 1; }; -/*********************************************************************/ +int VideoDevice_V4L2::Open() { + int i; + struct v4l2_capability vcap; + VideoDevCtrl vctl; + char txt[32]; + + debug ("Device: '%s'", conf_videodev.c_str()); + if (fd != -1) return 0; + + // + // open device and get device name and capabilities | O_NONBLOCK + if((fd = open(conf_videodev.c_str(), O_RDWR)) == -1){ + debug ("could not open device error: %s", strerror(errno)); + return 0; + } + + if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1) + strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card)); + debug ("VideoDevice_V4L2::Open Devicefile:%s Card:%s fd:%d", conf_videodev.c_str(), vcap.card, fd); + + debug ("Capabilities: %u", vcap.capabilities); + PrintCaps(vcap.capabilities); + debug ("Device Capabilities: %u", vcap.device_caps); + PrintCaps(vcap.device_caps); + + if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + debug ("Error: device has no video capture capabilities"); + return 0; + Close(); + } + + // + // query controls + struct v4l2_queryctrl queryctrl; + uint32_t min; + debug ("Get Controls"); + + vidctrls.clear(); + memset (&queryctrl, 0, sizeof (queryctrl)); + for (i = V4L2_CID_BASE; i < V4L2_CID_DETECT_CLASS_BASE+0x1000; i++) { + queryctrl.id = i; + if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) { + vctl.name = (char*)queryctrl.name; + vctl.id = queryctrl.id; + vctl.min = queryctrl.minimum; + vctl.max = queryctrl.maximum; + GetDevCtrl(queryctrl.id, &vctl.value); + vidctrls.push_back(vctl); + } + } + debug ("got %d controls.", vidctrls.size()); + + // + // check for cropping.. if we have it setup default + debug ("setup cropping"); + CLEAR (cropcap); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; // reset to default + + if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) { + debug ("VIDEOC_S_CROP Errorcode: %s", strerror(errno)); + } + } + + // + // prepare resolution and pixelformat + debug ("setup video resolution and pixeltype"); + CLEAR (fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (conf_width != -1 && conf_height != -1) { // resolution + fmt.fmt.pix.width = conf_width; + fmt.fmt.pix.height = conf_height; + } + else { + fmt.fmt.pix.width = 1920; + fmt.fmt.pix.height = 1080; + } + + // fixme: hardcoded video format????? + fmt.fmt.pix.pixelformat = convert_to_pixelformat(conf_videofmt); + fmt.fmt.pix.field = V4L2_FIELD_NONE; + if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) { + debug ("VIDIOC_S_FMT : %s", strerror (errno)); + close (fd); + fd = -1; + return 0; + } + + // Note VIDIOC_S_FMT may change width and height. + // Buggy driver paranoia. - as written in the v4l2 api documentation + min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) + fmt.fmt.pix.bytesperline = min; + min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; + if (fmt.fmt.pix.sizeimage < min) + fmt.fmt.pix.sizeimage = min; + conf_width = fmt.fmt.pix.width; + conf_height = fmt.fmt.pix.height; + snprintf (txt, 32, "%c%c%c%c", ((char*)&fmt.fmt.pix.pixelformat)[0], + ((char*)&fmt.fmt.pix.pixelformat)[1], + ((char*)&fmt.fmt.pix.pixelformat)[2], + ((char*)&fmt.fmt.pix.pixelformat)[3]); + conf_videofmt = txt; + PrintFmt (&fmt); + + // init buffers + switch (conf_iomode) { + case IOMODE_MMAP: + if (InitMMap() == 0) { + debug ("could not setup MMap (memory mapping)"); + Close(); + } + break; + case IOMODE_READ: + for (i = 0; i < VDEV_INBUFFERS; i++) { + inbuffer[i].size = fmt.fmt.pix.sizeimage; + inbuffer[i].data = (unsigned char*) malloc (fmt.fmt.pix.sizeimage); + } + + default: + break; + } + return 1; +}; -void VideoFrameFloat::AllocateFrame() { - printf ("VideoFrameFloat::AllocateFrame()\n"); - int memnewsize = width * height * 3 * sizeof(float); - if (memnewsize >= mem_allocated) return; - else if (memnewsize == 0) FreeFrame(); +int VideoDevice_V4L2::InitMMap() { + struct v4l2_requestbuffers bufreq; + struct v4l2_buffer bufinfo; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); - mem = (unsigned char *) realloc (mem, memnewsize); - mem_allocated = memnewsize; + int i; + + CLEAR(bufreq); + CLEAR(bufinfo); + + bufreq.count = VDEV_INBUFFERS; + bufreq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufreq.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_REQBUFS, &bufreq)) { + if (EINVAL == errno) { + printf("%s does not support memory mapping", conf_videodev.c_str()); + return 0; + } else { + printf ("%s:%d %s Error %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno)); + return 0; + } + } + + if (bufreq.count < 1) { + printf ( "Insufficient buffer memory on %s\n", conf_videodev.c_str()); + return 0; + } - if (mem == NULL) { - debug ("Error on allocation new frame\n"); - exit (1); + for (i = 0; i < VDEV_INBUFFERS; i++) { + CLEAR(bufinfo); + bufinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + bufinfo.memory = V4L2_MEMORY_MMAP; + bufinfo.index = i; + + if(ioctl(fd, VIDIOC_QUERYBUF, &bufinfo) < 0){ + perror("VIDIOC_QUERYBUF"); + exit(1); + } + + inbuffer[i].size = bufinfo.length; + inbuffer[i].data = (unsigned char*)mmap(NULL, bufinfo.length, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, bufinfo.m.offset); + if (inbuffer[i].data == MAP_FAILED) { + printf ( "%s:%d error on mmap %s\n", __FILE__, __LINE__, strerror(errno)); + exit (1); + } } + return 1; +} + + +int VideoDevice_V4L2::Close() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + if (fd >= 0) { + UnInit(); + close (fd); + fd = -1; + } + + return 1; }; -/*********************************************************************/ +int VideoDevice_V4L2::UnInit() { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + switch (conf_iomode) { + case IOMODE_READ: + for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) { + if (inbuffer[inbuffer_idx].data) { + free (inbuffer[inbuffer_idx].data); + inbuffer[inbuffer_idx].data = NULL; + inbuffer[inbuffer_idx].size = 0; + } + } + + break; + + case IOMODE_MMAP: + for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) + if (inbuffer[inbuffer_idx].data) { + if (-1 == munmap(inbuffer[inbuffer_idx].data, inbuffer[inbuffer_idx].size)){ + fprintf(stderr, "Fatal Error @ %s:%d munmap Error:%s\n", __FILE__, __LINE__, strerror(errno)); + exit(1); + } + inbuffer[inbuffer_idx].data = NULL; + inbuffer[inbuffer_idx].size = 0; + } + break; + + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + + + return 1; +}; -VideoDevice::VideoDevice() { - videofd = 0; + +int VideoDevice_V4L2::Start() { + enum v4l2_buf_type type; + + debug (""); + + if (Open() == 0) { + debug ("VideoDevice_V4L2::Start Open Device Failed."); + return 0; + } + + switch (conf_iomode) { + case IOMODE_READ: + /* Nothing to do. */ + break; + + case IOMODE_MMAP: + struct v4l2_buffer buf; + + for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) { + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = inbuffer_idx; + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + debug ("error on VIDIOC_QBUF %s", strerror(errno)); + return 0; + } + } + + inbuffer_idx = 0; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) { + debug ("VIDIOC_STREAMON %s", strerror(errno)); + return 0; + } + break; + + default: + debug ("wrong IOMODE?"); + return 0; + } + + ConvertStart(&cdata, fmt.fmt.pix.pixelformat); + + return 1; }; -VideoDevice::~VideoDevice() { +int VideoDevice_V4L2::Stop() { + enum v4l2_buf_type type; + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + ConvertStop(&cdata, fmt.fmt.pix.pixelformat); + + switch (conf_iomode) { + case IOMODE_READ: + /* Nothing to do. */ + break; + case IOMODE_MMAP: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) { + fprintf(stderr, "%s:%d VIDIOC_STREAMOFF Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return 0; + } + break; + default: + printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__); + return 0; + } + Close(); + return 1; +}; + +int VideoDevice_V4L2::GetFrame(VideoFrame *destframe) { + struct v4l2_buffer buf; + int len; + + if (destframe == NULL) return 0; + + switch (conf_iomode) { + case IOMODE_READ: + if ((len = read (fd, inbuffer[0].data, fmt.fmt.pix.sizeimage)) == -1) { + switch (errno) { + case EAGAIN: + return -1; + case EIO: + default: + debug ("v4l2_grab IOM_READ: %s dest:%p size:%d", strerror (errno), destframe, fmt.fmt.pix.sizeimage); + return 0; + } + } + else { + Convert(&cdata, destframe, inbuffer[0].data, len, + fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); + } + break; + + case IOMODE_MMAP: + CLEAR(buf); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { + switch (errno) { + case EAGAIN: + return -1; + + case EIO: + debug ("error on VIDIOC_DQBUF EIO %s", strerror(errno)); + return 0; + + default: + debug ("error on VIDIOC_DQBUF %s", strerror(errno)); + return 0; + } + } + + if (buf.index >= 0 && buf.index < VDEV_INBUFFERS) { + Convert(&cdata, destframe, inbuffer[buf.index].data, buf.bytesused, + fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); + } + + if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) { + debug ("error on VIDIOC_QBUF %s", strerror(errno)); + return 0; } + + if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0; + break; + + default: + debug ("wrong IOMODE?"); + return 0; + } + + return 1; }; -int VideoDevice::SetDevice() { - return 0; +int VideoDevice_V4L2::xioctl(int fd, int request, void *arg) { + int r; + int errnoioctl; + struct timeval to1; + struct timeval to2; + float to; + + gettimeofday(&to1, NULL); + do { + r = ioctl(fd, request, arg); + errnoioctl = errno; + gettimeofday(&to2, NULL); + to = (float)(to2.tv_sec - to1.tv_sec) + ((to2.tv_usec - to1.tv_usec) / 1000000.0); + } while (r == -1 && errnoioctl == EINTR && to < 2.0); + + return r; }; -int VideoDevice::Start(int w, int h) { - return 0; +int VideoDevice_V4L2::SetDevCtrl(unsigned int id, int value) { + struct v4l2_control ctrl; + + CLEAR(ctrl); + ctrl.id = id; + ctrl.value = value; + if (-1 == xioctl (fd, VIDIOC_S_CTRL, &ctrl)) { + return 0; + } + + return 1; }; -int VideoDevice::Stop() { - return 0; +int VideoDevice_V4L2::GetDevCtrl(unsigned int id, int *value) { + struct v4l2_control ctrl; + + CLEAR(ctrl); + ctrl.id = id; + if (-1 == xioctl (fd, VIDIOC_G_CTRL, &ctrl)) { + return 0; + } + *value = ctrl.value; + + return 1; }; -int VideoDevice::GetFrame(VideoFrame *destframe) { - return 0; +void VideoDevice_V4L2::PrintCaps(uint32_t caps) { + printf ("%s:%d %s Caps: %x\n", __FILE__, __LINE__, __FUNCTION__, caps); + + if (caps & V4L2_CAP_VIDEO_CAPTURE) printf (" V4L2_CAP_VIDEO_CAPTURE\n"); + if (caps & V4L2_CAP_EXT_PIX_FORMAT) printf (" V4L2_CAP_EXT_PIX_FORMAT\n"); +#ifdef V4L2_CAP_META_CAPTURE + if (caps & V4L2_CAP_META_CAPTURE) printf (" V4L2_CAP_META_CAPTURE\n"); +#endif + if (caps & V4L2_CAP_STREAMING) printf (" V4L2_CAP_STREAMING\n"); + if (caps & V4L2_CAP_DEVICE_CAPS) printf (" V4L2_CAP_DEVICE_CAPS\n"); + if (caps & V4L2_CAP_TUNER) printf (" V4L2_CAP_TUNER\n"); + if (caps & V4L2_CAP_MODULATOR) printf (" V4L2_CAP_MODULATOR\n"); + if (caps & V4L2_CAP_READWRITE) printf (" V4L2_CAP_READWRITE\n"); + if (caps & V4L2_CAP_ASYNCIO) printf (" V4L2_CAP_ASYNCIO\n"); }; + +void VideoDevice_V4L2::PrintFmt(struct v4l2_format *f) { + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + printf (" type : %u\n", f->type); + printf (" fmt.pix.width : %d\n", f->fmt.pix.width); + printf (" fmt.pix.height : %d\n", f->fmt.pix.height); + printf (" fmt.fmt.pix.pixelformat : %c%c%c%c\n", ((char*)&f->fmt.pix.pixelformat)[0], + ((char*)&f->fmt.pix.pixelformat)[1], + ((char*)&f->fmt.pix.pixelformat)[2], + ((char*)&f->fmt.pix.pixelformat)[3]); + printf (" fmt.pix.field : %d\n", f->fmt.pix.field); + printf (" fmt.pix.bytesperline : %d\n", f->fmt.pix.bytesperline); + printf (" fmt.pix.sizeimage : %d\n", f->fmt.pix.sizeimage); +}; diff --git a/video.h b/video.h index caa8792..2170170 100644 --- a/video.h +++ b/video.h @@ -23,8 +23,12 @@ #include #include -#include +#include "convert.h" +#include "inmemoryfile.h" +#include "miniwebcam.h" +#include "videoframe.h" +#define VDEV_INBUFFERS 3 enum { IOMODE_READ, IOMODE_MMAP @@ -41,49 +45,78 @@ struct jpg_error_mgr { typedef struct jpg_error_mgr *jpg_error_ptr; -// -// only contain 24bit each color 8Bit -class VideoFrame { - private: - virtual void AllocateFrame(); - protected: - void FreeFrame(); - int mem_allocated; - unsigned char *mem; - int height; - int width; - public: - VideoFrame(); - ~VideoFrame(); +struct { + unsigned int id; + int min; + int max; + int value; + std::string name; +} typedef VideoDevCtrl; - int GetHeight() { return height; }; - int GetWidth() { return width; }; - unsigned char *GetPixBuf() { return mem; }; +struct { + unsigned int size; + unsigned char* data; +} typedef VideoInBuffer; - int SetSize(int w, int h); -}; -class VideoFrameFloat : public VideoFrame { +#include "convert.h" + +class VideoDevice { private: - void AllocateFrame(); protected: + std::string conf_videodev; + std::string conf_videofmt; + int conf_width; + int conf_height; + int conf_iomode; + ConvertData cdata; public: + VideoDevice(); + ~VideoDevice(); + + virtual int SetDevice(std::string newdevice, int nwidth, int nheight) { return 0; }; + virtual int SetIOMode(int newiomode) { return 0; }; + virtual int Start() { return 0; }; + virtual int Stop() { return 0; }; + virtual int GetFrame (VideoFrame *destframe) { return 0; }; + virtual int SetDevCtrl(unsigned int id, int value) { return 0; }; + virtual int GetDevCtrl(unsigned int id, int *value) { return 0; }; }; -class VideoDevice { +class VideoDevice_V4L2 : public VideoDevice { private: - int videofd; + std::list vidctrls; + + int inbuffer_idx; + VideoInBuffer inbuffer[VDEV_INBUFFERS]; + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + struct v4l2_format fmt; + + int fd; // filedescriptor to the video device file + + int Open(); + int Close(); + int UnInit(); + int InitMMap(); + int xioctl(int fd, int request, void *arg); protected: public: - VideoDevice(); - ~VideoDevice(); - - int SetDevice(); - int Start(int w, int h); + VideoDevice_V4L2(); + ~VideoDevice_V4L2(); + + int SetDevice(std::string newdevice, int nwidth, int nheight); + int SetIOMode(int newiomode); + int Start(); int Stop(); - int GetFrame(VideoFrame *destframe); + int GetFrame (VideoFrame *destframe); + int SetDevCtrl(unsigned int id, int value); + int GetDevCtrl(unsigned int id, int *value); + + void PrintCaps(uint32_t caps); + void PrintFmt(struct v4l2_format *f); }; diff --git a/videoframe.cc b/videoframe.cc new file mode 100644 index 0000000..83bff74 --- /dev/null +++ b/videoframe.cc @@ -0,0 +1,144 @@ + +#include +#include +#include +#include + +#include "videoframe.h" +#include "inmemoryfile.h" + +VideoFrame::VideoFrame() { + mem = NULL; + width = 0; + height = 0; + mem_allocated = 0; +}; + +VideoFrame::~VideoFrame() { + FreeFrame(); +}; + + +void VideoFrame::FreeFrame() { + if (mem != NULL) { + free (mem); + mem = NULL; + width = 0; + height = 0; + mem_allocated = 0; + } +}; + + +void VideoFrame::AllocateFrame() { + int memnewsize = width * height * 3; + if (memnewsize < mem_allocated) return; + else if (memnewsize == 0) FreeFrame(); + + mem = (unsigned char *) realloc (mem, memnewsize); + mem_allocated = memnewsize; + + if (mem == NULL) { + debug ("Error on allocation new frame\n"); + exit (1); + } +}; + + +int VideoFrame::SetSize(int w, int h) { + if (w < 0 && h < 0) return 0; + + width = w; + height = h; + + AllocateFrame(); + + return 1; +}; + + +int VideoFrame::ConvertToJpeg(InMemoryFile *imf, int quality) { + unsigned char *outbuffer = NULL; + size_t outbuffersize; + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + + if (imf == NULL) return 0; + if (height == 0 || width == 0) return 0; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + jpeg_mem_dest(&cinfo, &outbuffer, &outbuffersize); + + cinfo.image_width = width; /* image width and height, in pixels */ + cinfo.image_height = height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, TRUE); + row_stride = width * 3; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = & mem[cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + imf->CopyFrom(outbuffer, outbuffersize); + free (outbuffer); + + return 1; +}; + + +int VideoFrame::TestScreen(int w, int h) { + int x, y; + unsigned char r, g, b; + + SetSize (w, h); + + r = g = b = 0; + for (x = 0; x < w; x++) { + for (g = 0, y = 0; y < h; y++) { + b = r + g; + + mem[3*(x + y * width) + 0] = r; + mem[3*(x + y * width) + 1] = g; + mem[3*(x + y * width) + 2] = b; + + g++; + } + r ++; + } + + return 1; +} + + +/*********************************************************************/ + + + +void VideoFrameFloat::AllocateFrame() { + printf ("VideoFrameFloat::AllocateFrame()\n"); + int memnewsize = width * height * 3 * sizeof(float); + if (memnewsize >= mem_allocated) return; + else if (memnewsize == 0) FreeFrame(); + + mem = (unsigned char *) realloc (mem, memnewsize); + mem_allocated = memnewsize; + + if (mem == NULL) { + debug ("Error on allocation new frame\n"); + exit (1); + } +}; + + + diff --git a/videoframe.h b/videoframe.h new file mode 100644 index 0000000..c2fdb94 --- /dev/null +++ b/videoframe.h @@ -0,0 +1,38 @@ +#ifndef _VIDEOFRAME_H_ +#define _VIDEOFRAME_H_ + +#include "inmemoryfile.h" +// +// only contain 24bit each color 8Bit +class VideoFrame { + private: + virtual void AllocateFrame(); + protected: + void FreeFrame(); + int mem_allocated; + unsigned char *mem; + int height; + int width; + public: + VideoFrame(); + ~VideoFrame(); + + int GetHeight() { return height; }; + int GetWidth() { return width; }; + unsigned char *GetPixBuf() { return mem; }; + + int SetSize(int w, int h); + int ConvertToJpeg(InMemoryFile *imf, int quality); + int TestScreen(int w, int h); +}; + + +class VideoFrameFloat : public VideoFrame { + private: + void AllocateFrame(); + protected: + public: +}; + + +#endif diff --git a/webserver.cc b/webserver.cc index 4e41dde..df87991 100644 --- a/webserver.cc +++ b/webserver.cc @@ -1,2 +1,57 @@ -#include "webserver.h" +#include + +#include "miniwebcam.h" +#include "UDPTCPNetwork.h" + +std::string GenerateHtmlFile(); +InMemoryFile GenerateJpgFile(VideoFrame *vf); + +int WebCamServer::HandleRequest (WebRequestBuffer *requestbuffer, WebServerClient *webclient) { + if (requestbuffer == NULL || webclient == NULL) return 0; + + std::string request = requestbuffer->GetRequest(); + printf ("SimpleWebServerClient::HandleRequest() Request:%s\n", request.c_str()); + + if (request.compare ("/") == 0) request = "/index.html"; + + if (request.find("/index.html") != std::string::npos) { + std::string htmlfile = GenerateHtmlFile(); + if (webclient->SendResponseFileFromMemory(requestbuffer, request, "", + (void*) htmlfile.c_str(), + strlen(htmlfile.c_str())) != 1) return 0; + } + else if (request.find("/snapshot.jpg") != std::string::npos) { + InMemoryFile jpgfile; + currentimage.ConvertToJpeg(&jpgfile, 99); + if (webclient->SendResponseFileFromMemory(requestbuffer, request, "", + (void*) jpgfile.mem, + jpgfile.memsize) != 1) return 0; + } + else { + return 0; + } + + requestbuffer->Clear(); + return 1; +}; + + +std::string GenerateHtmlFile() { + std::string res; + res = "MiniWebCam"; + res += ""; + res += "\n"; + res += ""; + return res; +}; + diff --git a/webserver.h b/webserver.h deleted file mode 100644 index e4472cb..0000000 --- a/webserver.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _WEBSERVER_H_ -#define _WEBSERVER_H_ - -#include "miniwebcam.h" - -#endif \ No newline at end of file