Started on Linux V4L2 integration
This commit is contained in:
parent
1ecf9b382e
commit
d615e3da53
@ -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
4
bootstrap_linux.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
@ -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;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
*/
|
||||
|
||||
#include <memory.h> // for memcpy
|
||||
#include "stream.h"
|
||||
#include "context.h"
|
||||
|
||||
|
||||
@ -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
143
linux/platformcontext.cpp
Normal 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
57
linux/platformcontext.h
Normal 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
|
||||
26
linux/platformdeviceinfo.h
Normal file
26
linux/platformdeviceinfo.h
Normal 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
364
linux/platformstream.cpp
Normal 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
77
linux/platformstream.h
Normal 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
|
||||
23
linux/tests/CMakeLists.txt
Normal file
23
linux/tests/CMakeLists.txt
Normal 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
143
linux/tests/main.cpp
Normal 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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user