Started on Linux V4L2 integration

This commit is contained in:
Niels Moseley 2017-07-12 01:23:49 +02:00
parent 1ecf9b382e
commit d615e3da53
12 changed files with 882 additions and 24 deletions

View File

@ -21,26 +21,46 @@ include_directories(include)
IF (WIN32)
# add files for WIN32
set (SOURCE common/libmain.cpp
common/context.cpp
common/logging.cpp
common/stream.cpp
win/platformcontext.cpp
win/platformstream.cpp)
# add files for WIN32
set (SOURCE common/libmain.cpp
common/context.cpp
common/logging.cpp
common/stream.cpp
win/platformcontext.cpp
win/platformstream.cpp)
# add windows-specific test application
add_subdirectory(win/tests)
# create the library
add_library(openpnp-capture SHARED ${SOURCE})
# add windows-specific test application
add_subdirectory(win/tests)
ELSEIF(APPLE)
set (SOURCE common/libmain.cpp
common/context.cpp
common/logging.cpp
common/stream.cpp)
set (SOURCE common/libmain.cpp
common/context.cpp
common/logging.cpp
common/stream.cpp)
# create the library
add_library(openpnp-capture SHARED ${SOURCE})
ELSEIF(UNIX)
# TODO: add files for UNIX/LINUX
message( FATAL_ERROR "Support for UNIX systems is pending." )
set (SOURCE common/libmain.cpp
common/context.cpp
common/logging.cpp
common/stream.cpp
linux/platformcontext.cpp
linux/platformstream.cpp)
# need pthreads for std::thread
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
add_library(openpnp-capture SHARED ${SOURCE})
target_link_libraries(openpnp-capture Threads::Threads)
# add linux-specific test application
add_subdirectory(linux/tests)
ENDIF()
add_library(openpnp-capture SHARED ${SOURCE})

4
bootstrap_linux.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
mkdir build
cd build
cmake ..

View File

@ -15,16 +15,16 @@ void LOG(uint32_t logLevel, const char *format, ...)
switch(logLevel)
{
case LOG_CRIT:
printf("[CRIT] ");
fprintf(stderr,"[CRIT] ");
break;
case LOG_ERR:
printf("[ERR ] ");
fprintf(stderr,"[ERR ] ");
break;
case LOG_INFO:
printf("[INFO] ");
fprintf(stderr,"[INFO] ");
break;
case LOG_DEBUG:
printf("[DBG ] ");
fprintf(stderr,"[DBG ] ");
break;
default:
break;

View File

@ -9,6 +9,7 @@
*/
#include <memory.h> // for memcpy
#include "stream.h"
#include "context.h"

View File

@ -12,14 +12,14 @@
#include <stdint.h>
//
//
//
#if defined(__clang__)
#define SO_IMPORT
#define SO_EXPORT
#elif defined(__GNUC__) || defined(__GNUG__)
#define SO_IMPORT __attribute__ ((dllexport))
#define SO_EXPORT __attribute__ ((dllexport))
#define SO_IMPORT
#define SO_EXPORT
#elif defined(_MSC_VER)
#define SO_IMPORT __declspec(dllimport)
#define SO_EXPORT __declspec(dllexport)

143
linux/platformcontext.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
OpenPnp-Capture: a video capture subsystem.
Windows platform code
Created by Niels Moseley on 7/6/17.
Copyright © 2017 Niels Moseley. All rights reserved.
Platform/implementation specific structures
and typedefs.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string>
#include <linux/videodev2.h>
#include "../common/logging.h"
#include "platformstream.h"
#include "platformcontext.h"
// a platform factory function needed by
// libmain.cpp
Context* createPlatformContext()
{
return new PlatformContext();
}
PlatformContext::PlatformContext() :
Context()
{
LOG(LOG_DEBUG, "Context created\n");
enumerateDevices();
}
PlatformContext::~PlatformContext()
{
}
bool PlatformContext::enumerateDevices()
{
int fd;
v4l2_capability video_cap;
//video_window video_win;
//video_picture video_pic;
LOG(LOG_INFO,"Enumerating devices\n");
const uint32_t maxDevices = 64; // FIXME: is this a sane number for linux?
uint32_t dcount = 0;
while(dcount < maxDevices)
{
char fname[100];
snprintf(fname, sizeof(fname), "/dev/video%d", dcount++);
if ((fd = ::open(fname, O_RDWR /* required */ | O_NONBLOCK)) == -1)
{
//LOG(LOG_ERR, "enumerateDevices: Can't open device %s\n", fname);
continue;
}
if (ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1)
{
::close(fd);
LOG(LOG_ERR, "enumerateDevices: Can't get capabilities\n");
continue;
}
else
{
LOG(LOG_INFO,"Name: '%s'\n", video_cap.card);
LOG(LOG_INFO,"Path: '%s'\n", fname);
LOG(LOG_INFO,"capflags = %08X\n", video_cap.capabilities);
LOG(LOG_INFO,"devflags = %08X\n", video_cap.device_caps);
if ((video_cap.device_caps & V4L2_CAP_READWRITE) != 0)
{
LOG(LOG_INFO,"read/write supported\n");
}
else
{
LOG(LOG_INFO,"read/write NOT supported\n");
}
if ((video_cap.device_caps & V4L2_CAP_STREAMING) != 0)
{
LOG(LOG_INFO,"streaming I/O supported\n");
}
else
{
LOG(LOG_INFO,"streaming I/O NOT supported\n");
}
if ((video_cap.device_caps & V4L2_CAP_ASYNCIO) != 0)
{
LOG(LOG_INFO,"async I/O supported\n");
}
else
{
LOG(LOG_INFO,"async I/O NOT supported\n");
}
//printf("Minimum size:\t%d x %d\n", video_cap.minwidth, video_cap.minheight);
//printf("Maximum size:\t%d x %d\n", video_cap.maxwidth, video_cap.maxheight);
}
if ((video_cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) != 0)
{
platformDeviceInfo* dinfo = new platformDeviceInfo();
dinfo->m_name = std::string((const char*)video_cap.card);
dinfo->m_devicePath = std::string(fname);
m_devices.push_back(dinfo);
}
#if 0
if (ioctl(fd, VIDIOCGWIN, &video_win) == -1)
{
LOG(LOG_ERR, "enumerateDevices: Can't get window information\n");
}
else
{
LOG(LOG_INFO, "Current size:\t%d x %d\n", video_win.width, video_win.height);
}
if (ioctl(fd, VIDIOCGPICT, &video_pic) == -1)
{
LOG(LOG_ERR, "enumerateDevices: Can't get picture information");
}
else
{
printf("Current depth:\t%d\n", video_pic.depth);
}
#endif
close(fd);
}
return true;
}

57
linux/platformcontext.h Normal file
View File

@ -0,0 +1,57 @@
/*
OpenPnp-Capture: a video capture subsystem.
Created by Niels Moseley on 7/11/17.
Copyright © 2017 Niels Moseley. All rights reserved.
Platform independent context class to keep track
of the global state.
*/
#ifndef openpnp_win32platformcontext_h
#define openpnp_win32platformcontext_h
#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <string>
#include <map>
#include <stdint.h>
#include "openpnp-capture.h"
#pragma comment(lib, "strmiids")
#include "platformdeviceinfo.h"
#include "../common/context.h"
/** context base class keeps track of all the platform independent
objects and information */
class PlatformContext : public Context
{
public:
/** Create a context for the library.
Device enumeration is perform in the constructor,
so all devices must be present in the system when
the Context is created or devices will not be found.
Re-enumeration support is pending.
*/
PlatformContext();
virtual ~PlatformContext();
protected:
/** Enumerate V4L capture devices and put their
information into the m_devices array
Implement this function in a platform-dependent
derived class.
*/
virtual bool enumerateDevices();
};
#endif

View File

@ -0,0 +1,26 @@
#ifndef platformdeviceinfo_h
#define platformdeviceinfo_h
#include <linux/videodev2.h>
#include <string>
#include "../common/deviceinfo.h"
/** device information struct/object */
class platformDeviceInfo : public deviceInfo
{
public:
platformDeviceInfo() : deviceInfo()
{
}
virtual ~platformDeviceInfo()
{
}
std::string m_devicePath; ///< unique device path
};
#endif

364
linux/platformstream.cpp Normal file
View File

@ -0,0 +1,364 @@
/*
OpenPnp-Capture: a video capture subsystem.
Windows platform code
Created by Niels Moseley on 7/6/17.
Copyright © 2017 Niels Moseley. All rights reserved.
Stream class
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <memory.h>
#include <string>
#include "platformdeviceinfo.h"
#include "platformstream.h"
#include "platformcontext.h"
#define CLEAR(x) memset(&(x), 0, sizeof(x))
Stream* createPlatformStream()
{
return new PlatformStream();
}
int xioctl(int fh, int request, void *arg)
{
int r;
do
{
r = ioctl(fh, request, arg);
} while ((r == -1) && (errno == EINTR));
return r;
}
// **********************************************************************
// Capture thread/function
// **********************************************************************
void captureThreadFunction(PlatformStream *stream, int fd, size_t bufferSizeBytes)
{
if (stream == nullptr)
{
return;
}
LOG(LOG_DEBUG, "capture thread running (deviceHandle = %08X) ...\n", fd);
// create local frame buffer
std::vector<uint8_t> buffer(bufferSizeBytes);
// FIXME: For now, weĺl just rely on the read to fail
// when the PlatformStream closes the file
// descriptor. This doesn't feel very professional,
// but it should work :)
while(!stream->getThreadQuitState())
{
ssize_t actualBytesRead = ::read(fd, &buffer[0], bufferSizeBytes);
if (actualBytesRead < 0)
{
LOG(LOG_DEBUG, "capture thread exited (errno %d).\n", errno);
return; //exit thread
}
// read will only return complete buffers
stream->threadSubmitBuffer(&buffer[0], actualBytesRead);
LOG(LOG_INFO, "yay\n");
}
}
void captureThreadFunctionAsync(PlatformStream *stream, int fd, size_t bufferSizeBytes)
{
//https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/capture.c.html
const uint32_t nBuffers = 5;
struct CaptureBufferInfo
{
uint8_t *start;
size_t length;
};
CaptureBufferInfo buffers[nBuffers];
if (stream == nullptr)
{
return;
}
// ****************************************
// create queue buffers
// ****************************************
v4l2_buf_type bufferType;
for (uint32_t i = 0; i < nBuffers; ++i)
{
v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
LOG(LOG_ERR,"VIDIOC_QBUF failed (errno=%d)\n", errno);
}
}
bufferType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMON, &bufferType) == -1)
{
LOG(LOG_ERR,"VIDIOC_STREAMON failed (errno=%d)\n", errno);
}
while(!stream->getThreadQuitState())
{
fd_set fds;
struct timeval tv;
int result;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
result = select(fd + 1, &fds, NULL, NULL, &tv);
if (result == -1)
{
if (errno == EINTR)
{
continue;
}
LOG(LOG_ERR,"Select failed (errno=%d)\n", errno);
return;
}
else if (result == 0)
{
LOG(LOG_ERR,"Select timeout\n");
return;
}
// ****************************************
// read the frame
// ****************************************
v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1)
{
switch (errno)
{
case EAGAIN:
return;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
LOG(LOG_ERR, "VIDIOC_DQBUF error\n");
return;
}
}
//assert(buf.index < nBuffers);
stream->threadSubmitBuffer(buffers[buf.index].start, buf.bytesused);
// re-queue the buffer
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
LOG(LOG_ERR, "VIDIOC_DQBUF error\n");
return;
}
} // while
bufferType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMOFF, &bufferType) == -1)
{
LOG(LOG_ERR, "VIDIOC_STREAMOFF error\n");
return;
}
}
// **********************************************************************
// PlatformStream
// **********************************************************************
PlatformStream::PlatformStream() :
Stream(),
m_quitThread(false),
m_helperThread(nullptr)
{
}
PlatformStream::~PlatformStream()
{
close();
}
void PlatformStream::close()
{
LOG(LOG_INFO, "closing stream\n");
m_owner = nullptr;
m_width = 0;
m_height = 0;
m_frameBuffer.resize(0);
m_isOpen = false;
m_quitThread = true;
::close(m_deviceHandle);
if (m_helperThread != nullptr)
{
m_helperThread->join();
delete m_helperThread;
m_helperThread = nullptr;
}
m_deviceHandle = -1;
}
void test(size_t bufferSizeBytes)
{
}
bool PlatformStream::open(Context *owner, deviceInfo *device, uint32_t width, uint32_t height, uint32_t fourCC)
{
if (m_isOpen)
{
LOG(LOG_INFO,"open() was called on an active stream.\n");
close();
}
if (owner == nullptr)
{
LOG(LOG_ERR,"open() was with owner=NULL!\n");
return false;
}
if (device == nullptr)
{
LOG(LOG_ERR,"open() was with device=NULL!\n");
return false;
}
platformDeviceInfo *dinfo = dynamic_cast<platformDeviceInfo*>(device);
if (dinfo == NULL)
{
LOG(LOG_CRIT, "Could not cast deviceInfo* to platfromDeviceInfo*!");
return false;
}
m_owner = owner;
m_frames = 0;
m_width = 0;
m_height = 0;
//m_deviceHandle = ::open(dinfo->m_devicePath.c_str(), O_RDWR /* required */ | O_NONBLOCK);
m_deviceHandle = ::open(dinfo->m_devicePath.c_str(), O_RDWR /* required */);
if (m_deviceHandle < 0)
{
LOG(LOG_CRIT, "Could not open device %s (errno = %d)\n", dinfo->m_devicePath.c_str(), errno);
close();
return false;
}
m_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(m_deviceHandle, VIDIOC_G_FMT, &m_fmt) == -1)
{
LOG(LOG_CRIT, "Could not query default format (errno = %d)\n", errno);
close();
return false;
}
LOG(LOG_INFO, "Format buffer type: %d\n", m_fmt.type);
if (m_fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
{
LOG(LOG_ERR, "Buffer type (%d) not supported!\n", m_fmt.type);
close();
return false;
}
m_width = m_fmt.fmt.pix.width;
m_height = m_fmt.fmt.pix.height;
LOG(LOG_INFO, "Width = %d pixels\n", m_fmt.fmt.pix.width);
LOG(LOG_INFO, "Height = %d pixels\n", m_fmt.fmt.pix.height);
LOG(LOG_INFO, "FOURCC = %s\n", genFOURCCstring(m_fmt.fmt.pix.pixelformat).c_str());
m_isOpen = true;
// create the helper thread to read from the device
m_quitThread = false;
#if 0
m_helperThread = new std::thread(&captureThreadFunction, this,
m_deviceHandle, m_width*m_height*4);
#else
m_helperThread = new std::thread(&captureThreadFunctionAsync, this,
m_deviceHandle, m_width*m_height*4);
#endif
return true;
}
uint32_t PlatformStream::getFOURCC()
{
if (m_isOpen)
{
return m_fmt.fmt.pix.pixelformat;
}
else
{
return 0;
}
}
bool PlatformStream::setExposure(int32_t value)
{
return false;
}
bool PlatformStream::setAutoExposure(bool enabled)
{
return false;
}
bool PlatformStream::getExposureLimits(int32_t *emin, int32_t *emax)
{
return false;
}
std::string PlatformStream::genFOURCCstring(uint32_t v)
{
std::string result;
for(uint32_t i=0; i<4; i++)
{
result += static_cast<char>(v & 0xFF);
v >>= 8;
}
return result;
}

77
linux/platformstream.h Normal file
View File

@ -0,0 +1,77 @@
/*
OpenPnp-Capture: a video capture subsystem.
Windows platform code
Created by Niels Moseley on 7/6/17.
Copyright © 2017 Niels Moseley. All rights reserved.
Stream class
*/
#ifndef win32platform_stream_h
#define win32platform_stream_h
#include <stdint.h>
#include <vector>
#include <mutex>
#include <thread>
#include <linux/videodev2.h>
#include "../common/logging.h"
#include "../common/stream.h"
class Context; // pre-declaration
class PlatformStream; // pre-declaration
class PlatformStream : public Stream
{
public:
PlatformStream();
virtual ~PlatformStream();
/** Open a capture stream to a device and request a specific (internal) stream format.
When succesfully opened, capturing starts immediately.
*/
virtual bool open(Context *owner, deviceInfo *device, uint32_t width, uint32_t height, uint32_t fourCC) override;
/** Close a capture stream */
virtual void close() override;
/** Return the FOURCC media type of the stream */
virtual uint32_t getFOURCC() override;
virtual bool setExposure(int32_t value) override;
virtual bool setAutoExposure(bool enabled) override;
virtual bool getExposureLimits(int32_t *min, int32_t *max) override;
/** called by the capture thread/function to query if it
should quit */
bool getThreadQuitState() const
{
return m_quitThread;
}
/** public submit buffer so the capture thread/function
can access it */
void threadSubmitBuffer(uint8_t *ptr, size_t bytes)
{
submitBuffer(ptr, bytes);
}
protected:
/** generate FOURCC string from a uint32 */
std::string genFOURCCstring(uint32_t v);
int m_deviceHandle; ///< V4L2 device handle
v4l2_format m_fmt; ///< V4L2 frame format
bool m_quitThread; ///< if true, captureThreadFunction should return
std::thread *m_helperThread; ///< helper object threading control
};
#endif

View File

@ -0,0 +1,23 @@
#
# CMAKE build file for OpenPnP Capture library
#
# This generates make files for several build systems,
# such as GNU Make, Ninja and visual studio
#
# When invoking on Windows systems, make sure the
# compiler (Visual Studio) is in the search path.
#
# Author: Niels A. Moseley
#
cmake_minimum_required(VERSION 3.0)
project (openpnp-capture-test)
# add include directory
include_directories(../include ..)
set (SOURCE main.cpp ../../common/logging.cpp)
add_executable(openpnp-capture-test ${SOURCE})
target_link_libraries(openpnp-capture-test openpnp-capture)

143
linux/tests/main.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
openpnp test application
Niels Moseley
*/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <unistd.h>
#include "openpnp-capture.h"
#include "../common/context.h"
int main(int argc, char*argv[])
{
printf("OpenPNP Capture Test Program\n");
Cap_setLogLevel(7);
CapContext ctx = Cap_createContext();
uint32_t deviceCount = Cap_getDeviceCount(ctx);
printf("Number of devices: %d\n", deviceCount);
for(uint32_t i=0; i<deviceCount; i++)
{
printf("ID %d -> %s\n", i, Cap_getDeviceName(ctx,i));
}
int32_t streamID = Cap_openStream(ctx, 0, 0, 0, 0);
printf("Stream ID = %d\n", streamID);
if (Cap_isOpenStream(ctx, streamID) == 1)
{
printf("Stream is open\n");
}
else
{
printf("Stream is closed (?)\n");
}
printf("Press Q to exit..\n");
std::vector<uint8_t> m_buffer;
m_buffer.resize(640*480*3);
Cap_setAutoExposure(ctx, streamID, 1);
#if 1
uint32_t counter = 0;
uint32_t tries = 0;
while(counter < 30)
{
usleep(50000);
printf("%d", Cap_getStreamFrameCount(ctx, streamID));
if (Cap_hasNewFrame(ctx, streamID) == 1)
{
Cap_captureFrame(ctx, streamID, &m_buffer[0], m_buffer.size());
counter++;
}
tries++;
if (tries == 1000)
{
break;
}
};
#endif
Cap_setAutoExposure(ctx, streamID, 0);
// wait for a new frame ..
//while (Cap_hasNewFrame(ctx, streamID) == 0) {};
if (Cap_captureFrame(ctx, streamID, &m_buffer[0], m_buffer.size()) == CAPRESULT_OK)
{
printf("Buffer captured!\n");
FILE *fout = fopen("image.ppm", "wb");
fprintf(fout, "P6 640 480 255\n"); // PGM header
const uint32_t height = 480;
const uint32_t width = 640;
// exchange BGR to RGB
uint32_t idx = 0;
for(uint32_t i=0; i<width*height; i++)
{
uint8_t b = m_buffer[idx];
uint8_t g = m_buffer[idx+1];
uint8_t r = m_buffer[idx+2];
m_buffer[idx++] = r;
m_buffer[idx++] = g;
m_buffer[idx++] = b;
}
// and upside-down :)
const uint32_t stride = 3;
const size_t lineBytes = width * stride;
uint8_t *row = new uint8_t[lineBytes];
uint8_t *low = &m_buffer[0];
uint8_t *high = &m_buffer[(height - 1) * lineBytes];
for (; low < high; low += lineBytes, high -= lineBytes) {
memcpy(row, low, lineBytes);
memcpy(low, high, lineBytes);
memcpy(high, row, lineBytes);
}
delete[] row;
fwrite(&m_buffer[0], 1, m_buffer.size(), fout);
fclose(fout);
}
char c = 0;
int32_t v = 0;
while((c != 'q') && (c != 'Q'))
{
c = getchar();
switch(c)
{
case '+':
printf("+");
Cap_setExposure(ctx, streamID, ++v);
break;
case '-':
printf("-");
Cap_setExposure(ctx, streamID, --v);
break;
case '0':
printf("0");
v = 0;
Cap_setExposure(ctx, streamID, v);
break;
}
}
Cap_closeStream(ctx, streamID);
CapResult result = Cap_releaseContext(ctx);
return 0;
}