Moved test/v4l2_to_png to src/hyperion-v4l2; Added json backend temporarily

Former-commit-id: 8c4e0ef7add8016c522d0b4c6f4df8886b905e36
This commit is contained in:
johan 2014-01-25 17:35:06 +01:00
parent 1981f6e307
commit 6723c7bf2b
12 changed files with 533 additions and 148 deletions

View File

@ -120,7 +120,7 @@ void OptionsParser::usage() const {
for(i = parameters.parameters.begin();
i != parameters.parameters.end(); i++)
{
cerr.width(30);
cerr.width(31);
cerr << std::left << " " + (*i)->usageLine();
cerr.width(40);

View File

@ -3,9 +3,6 @@
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/protoserver)
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/protoserver)
# add protocol buffers
find_package(Protobuf REQUIRED)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${PROTOBUF_INCLUDE_DIRS})

View File

@ -1,2 +1,5 @@
add_subdirectory(hyperiond)
add_subdirectory(hyperion-remote)
if (ENABLE_V4L2)
add_subdirectory(hyperion-v4l2)
endif (ENABLE_V4L2)

View File

@ -0,0 +1,54 @@
cmake_minimum_required(VERSION 2.8)
project(hyperion-v4l2)
# add protocol buffers
find_package(Protobuf REQUIRED)
# find Qt4
find_package(Qt4 REQUIRED QtCore QtGui QtNetwork)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${PROTOBUF_INCLUDE_DIRS}
${QT_INCLUDES}
)
set(Hyperion_V4L2_HEADERS
V4L2Grabber.h
ProtoConnection.h
)
set(Hyperion_V4L2_SOURCES
hyperion-v4l2.cpp
V4L2Grabber.cpp
ProtoConnection.cpp
)
set(Hyperion_V4L2_PROTOS
${CMAKE_CURRENT_SOURCE_DIR}/../../libsrc/protoserver/message.proto
)
protobuf_generate_cpp(Hyperion_V4L2_PROTO_SRCS Hyperion_V4L2_PROTO_HDRS
${Hyperion_V4L2_PROTOS}
)
add_executable(hyperion-v4l2
${Hyperion_V4L2_HEADERS}
${Hyperion_V4L2_SOURCES}
${Hyperion_V4L2_PROTO_SRCS}
${Hyperion_V4L2_PROTO_HDRS}
)
target_link_libraries(hyperion-v4l2
getoptPlusPlus
jsoncpp
hyperion-utils
${PROTOBUF_LIBRARIES}
pthread
)
qt4_use_modules(hyperion-v4l2
Core
Gui
Network)

View File

@ -0,0 +1,181 @@
// stl includes
#include <stdexcept>
// Qt includes
#include <QRgb>
// hyperion-v4l2 includes
#include "ProtoConnection.h"
ProtoConnection::ProtoConnection(const std::string & a) :
_socket()
{
QString address(a.c_str());
QStringList parts = address.split(":");
if (parts.size() != 2)
{
throw std::runtime_error(QString("Wrong address: unable to parse address (%1)").arg(address).toStdString());
}
bool ok;
uint16_t port = parts[1].toUShort(&ok);
if (!ok)
{
throw std::runtime_error(QString("Wrong address: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
}
_socket.connectToHost(parts[0], port);
if (!_socket.waitForConnected())
{
throw std::runtime_error("Unable to connect to host");
}
std::cout << "Connected to " << a << std::endl;
}
ProtoConnection::~ProtoConnection()
{
_socket.close();
}
void ProtoConnection::setColor(std::vector<QColor> colors, int priority, int duration)
{
// create command
Json::Value command;
command["command"] = "color";
command["priority"] = priority;
Json::Value & rgbValue = command["color"];
for (const QColor & color : colors)
{
rgbValue.append(color.red());
rgbValue.append(color.green());
rgbValue.append(color.blue());
}
if (duration > 0)
{
command["duration"] = duration;
}
// send command message
Json::Value reply = sendMessage(command);
// parse reply message
parseReply(reply);
}
void ProtoConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
{
// ensure the image has RGB888 format
QByteArray binaryImage = QByteArray::fromRawData(reinterpret_cast<const char *>(image.memptr()), image.width() * image.height() * 3);
const QByteArray base64Image = binaryImage.toBase64();
// create command
Json::Value command;
command["command"] = "image";
command["priority"] = priority;
command["imagewidth"] = image.width();
command["imageheight"] = image.height();
command["imagedata"] = std::string(base64Image.data(), base64Image.size());
if (duration > 0)
{
command["duration"] = duration;
}
// send command message
Json::Value reply = sendMessage(command);
// parse reply message
parseReply(reply);
}
void ProtoConnection::clear(int priority)
{
std::cout << "Clear priority channel " << priority << std::endl;
// create command
Json::Value command;
command["command"] = "clear";
command["priority"] = priority;
// send command message
Json::Value reply = sendMessage(command);
// parse reply message
parseReply(reply);
}
void ProtoConnection::clearAll()
{
std::cout << "Clear all priority channels" << std::endl;
// create command
Json::Value command;
command["command"] = "clearall";
// send command message
Json::Value reply = sendMessage(command);
// parse reply message
parseReply(reply);
}
Json::Value ProtoConnection::sendMessage(const Json::Value & message)
{
// serialize message (FastWriter already appends a newline)
std::string serializedMessage = Json::FastWriter().write(message);
// write message
_socket.write(serializedMessage.c_str());
if (!_socket.waitForBytesWritten())
{
throw std::runtime_error("Error while writing data to host");
}
// read reply data
QByteArray serializedReply;
while (!serializedReply.contains('\n'))
{
// receive reply
if (!_socket.waitForReadyRead())
{
throw std::runtime_error("Error while reading data from host");
}
serializedReply += _socket.readAll();
}
int bytes = serializedReply.indexOf('\n') + 1; // Find the end of message
// parse reply data
Json::Reader jsonReader;
Json::Value reply;
if (!jsonReader.parse(serializedReply.constData(), serializedReply.constData() + bytes, reply))
{
throw std::runtime_error("Error while parsing reply: invalid json");
}
return reply;
}
bool ProtoConnection::parseReply(const Json::Value &reply)
{
bool success = false;
std::string reason = "No error info";
try
{
success = reply.get("success", false).asBool();
if (!success)
reason = reply.get("error", reason).asString();
}
catch (const std::runtime_error &)
{
// Some json parsing error: ignore and set parsing error
}
if (!success)
{
throw std::runtime_error("Error: " + reason);
}
return success;
}

View File

@ -0,0 +1,89 @@
#pragma once
// stl includes
#include <string>
// Qt includes
#include <QColor>
#include <QImage>
#include <QTcpSocket>
#include <QMap>
// hyperion util
#include <utils/Image.h>
#include <utils/ColorRgb.h>
// jsoncpp includes
#include <json/json.h>
///
/// Connection class to setup an connection to the hyperion server and execute commands
///
class ProtoConnection
{
public:
///
/// Constructor
///
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
///
ProtoConnection(const std::string & address);
///
/// Destructor
///
~ProtoConnection();
///
/// Set all leds to the specified color
///
/// @param color The color
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setColor(std::vector<QColor> color, int priority, int duration);
///
/// Set the leds according to the given image (assume the image is stretched to the display size)
///
/// @param image The image
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setImage(const Image<ColorRgb> & image, int priority, int duration);
///
/// Clear the given priority channel
///
/// @param priority The priority
///
void clear(int priority);
///
/// Clear all priority channels
///
void clearAll();
private:
///
/// Send a json command message and receive its reply
///
/// @param message The message to send
///
/// @return The returned reply
///
Json::Value sendMessage(const Json::Value & message);
///
/// Parse a reply message
///
/// @param reply The received reply
///
/// @return true if the reply indicates success
///
bool parseReply(const Json::Value & reply);
private:
/// The TCP-Socket with the connection to the server
QTcpSocket _socket;
};

View File

@ -12,9 +12,6 @@
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <QImage>
#include <QRgb>
#include "V4L2Grabber.h"
#define CLEAR(x) memset(&(x), 0, sizeof(x))
@ -37,18 +34,20 @@ static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, u
}
V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) :
V4L2Grabber::V4L2Grabber(const std::string &device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation) :
_deviceName(device),
_ioMethod(IO_METHOD_MMAP),
_fileDescriptor(-1),
_buffers(),
_width(0),
_height(0),
_width(width),
_height(height),
_cropWidth(cropHorizontal),
_cropHeight(cropVertical),
_frameDecimation(std::max(1, frameDecimation)),
_pixelDecimation(std::max(1, pixelDecimation)),
_currentFrame(0)
_currentFrame(0),
_callback(nullptr),
_callbackArg(nullptr)
{
open_device();
init_device(videoStandard, input);
@ -60,6 +59,12 @@ V4L2Grabber::~V4L2Grabber()
close_device();
}
void V4L2Grabber::setCallback(V4L2Grabber::ImageCallback callback, void *arg)
{
_callback = callback;
_callbackArg = arg;
}
void V4L2Grabber::start()
{
start_capturing();
@ -343,6 +348,7 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
break;
}
// get the current settings
struct v4l2_format fmt;
CLEAR(fmt);
@ -358,6 +364,32 @@ void V4L2Grabber::init_device(VideoStandard videoStandard, int input)
exit(EXIT_FAILURE);
}
if (_width > 0 || _height > 0)
{
if (_width > 0)
{
fmt.fmt.pix.width = _width;
}
if (fmt.fmt.pix.height > 0)
{
fmt.fmt.pix.height = _height;
}
// set the settings
if (-1 == xioctl(VIDIOC_S_FMT, &fmt))
{
errno_exit("VIDIOC_S_FMT");
}
// get the format settings again
// (the size may not have been accepted without an error)
if (-1 == xioctl(VIDIOC_G_FMT, &fmt))
{
errno_exit("VIDIOC_G_FMT");
}
}
// store width & height
_width = fmt.fmt.pix.width;
_height = fmt.fmt.pix.height;
@ -468,6 +500,8 @@ void V4L2Grabber::stop_capturing()
int V4L2Grabber::read_frame()
{
bool rc = false;
struct v4l2_buffer buf;
switch (_ioMethod) {
@ -490,7 +524,7 @@ int V4L2Grabber::read_frame()
}
}
process_image(_buffers[0].start, size);
rc = process_image(_buffers[0].start, size);
break;
case IO_METHOD_MMAP:
@ -518,7 +552,7 @@ int V4L2Grabber::read_frame()
assert(buf.index < _buffers.size());
process_image(_buffers[buf.index].start, buf.bytesused);
rc = process_image(_buffers[buf.index].start, buf.bytesused);
if (-1 == xioctl(VIDIOC_QBUF, &buf))
{
@ -558,7 +592,7 @@ int V4L2Grabber::read_frame()
}
}
process_image((void *)buf.m.userptr, buf.bytesused);
rc = process_image((void *)buf.m.userptr, buf.bytesused);
if (-1 == xioctl(VIDIOC_QBUF, &buf))
{
@ -567,10 +601,10 @@ int V4L2Grabber::read_frame()
break;
}
return 1;
return rc ? 1 : 0;
}
void V4L2Grabber::process_image(const void *p, int size)
bool V4L2Grabber::process_image(const void *p, int size)
{
if (++_currentFrame >= _frameDecimation)
{
@ -584,14 +618,15 @@ void V4L2Grabber::process_image(const void *p, int size)
{
process_image(reinterpret_cast<const uint8_t *>(p));
_currentFrame = 0; // restart counting
return true;
}
}
return false;
}
void V4L2Grabber::process_image(const uint8_t * data)
{
std::cout << "process image" << std::endl;
int width = (_width - 2 * _cropWidth + _pixelDecimation/2) / _pixelDecimation;
int height = (_height - 2 * _cropHeight + _pixelDecimation/2) / _pixelDecimation;
@ -611,9 +646,10 @@ void V4L2Grabber::process_image(const uint8_t * data)
}
}
// store as PNG
QImage pngImage((const uint8_t *) image.memptr(), width, height, 3*width, QImage::Format_RGB888);
pngImage.save("screenshot.png");
if (_callback != nullptr)
{
(*_callback)(_callbackArg, image);
}
}
int V4L2Grabber::xioctl(int request, void *arg)

View File

@ -14,14 +14,18 @@
class V4L2Grabber
{
public:
typedef void (*ImageCallback)(void * arg, const Image<ColorRgb> & image);
enum VideoStandard {
PAL, NTSC, NO_CHANGE
};
public:
V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation);
V4L2Grabber(const std::string & device, int input, VideoStandard videoStandard, int width, int height, int cropHorizontal, int cropVertical, int frameDecimation, int pixelDecimation);
virtual ~V4L2Grabber();
void setCallback(ImageCallback callback, void * arg);
void start();
void capture(int frameCount = -1);
@ -49,7 +53,7 @@ private:
int read_frame();
void process_image(const void *p, int size);
bool process_image(const void *p, int size);
void process_image(const uint8_t *p);
@ -83,4 +87,7 @@ private:
const int _pixelDecimation;
int _currentFrame;
ImageCallback _callback;
void * _callbackArg;
};

View File

@ -0,0 +1,143 @@
// STL includes
#include <csignal>
#include <iomanip>
// QT includes
#include <QImage>
// getoptPlusPLus includes
#include <getoptPlusPlus/getoptpp.h>
#include "V4L2Grabber.h"
#include "ProtoConnection.h"
using namespace vlofgren;
/// Data parameter for the video standard
typedef vlofgren::PODParameter<V4L2Grabber::VideoStandard> VideoStandardParameter;
namespace vlofgren {
/// Translates a string (as passed on the commandline) to a color standard
///
/// @param[in] s The string (as passed on the commandline)
/// @return The color standard
/// @throws Parameter::ParameterRejected If the string did not result in a video standard
template<>
V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected)
{
QString input = QString::fromStdString(s).toLower();
if (input == "pal")
{
return V4L2Grabber::PAL;
}
else if (input == "ntsc")
{
return V4L2Grabber::NTSC;
}
else if (input == "no-change")
{
return V4L2Grabber::NO_CHANGE;
}
throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE");
return V4L2Grabber::NO_CHANGE;
}
}
// save the image as screenshot
void saveScreenshot(void *, const Image<ColorRgb> & image)
{
// store as PNG
QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888);
pngImage.save("screenshot.png");
}
// send the image to Hyperion
void sendImage(void * arg, const Image<ColorRgb> & image)
{
ProtoConnection * connection = static_cast<ProtoConnection *>(arg);
connection->setImage(image, 50, 200);
}
int main(int argc, char** argv)
{
try
{
// create the option parser and initialize all parameters
OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface");
ParameterSet & parameters = optionParser.getParameters();
StringParameter & argDevice = parameters.add<StringParameter> ('d', "device", "The device to use [default=/dev/video0]");
VideoStandardParameter & argVideoStandard = parameters.add<VideoStandardParameter>('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]");
IntParameter & argInput = parameters.add<IntParameter> (0x0, "input", "Input channel (optional)");
IntParameter & argWidth = parameters.add<IntParameter> (0x0, "width", "Try to set the width of the video input (optional)");
IntParameter & argHeight = parameters.add<IntParameter> (0x0, "height", "Try to set the height of the video input (optional)");
IntParameter & argCropWidth = parameters.add<IntParameter> (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]");
IntParameter & argCropHeight = parameters.add<IntParameter> (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]");
IntParameter & argSizeDecimation = parameters.add<IntParameter> ('s', "size-decimator", "Decimation factor for the output size [default=1]");
IntParameter & argFrameDecimation = parameters.add<IntParameter> ('f', "frame-decimator","Decimation factor for the video frames [default=1]");
SwitchParameter<> & argScreenshot = parameters.add<SwitchParameter<>> (0x0, "screenshot", "Take a single screenshot, save it to file and quit");
StringParameter & argAddress = parameters.add<StringParameter> ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]");
IntParameter & argPriority = parameters.add<IntParameter> ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]");
SwitchParameter<> & argHelp = parameters.add<SwitchParameter<>> ('h', "help", "Show this help message and exit");
// set defaults
argDevice.setDefault("/dev/video0");
argVideoStandard.setDefault(V4L2Grabber::PAL);
argInput.setDefault(-1);
argWidth.setDefault(-1);
argHeight.setDefault(-1);
argCropWidth.setDefault(0);
argCropHeight.setDefault(0);
argSizeDecimation.setDefault(1);
argFrameDecimation.setDefault(1);
argAddress.setDefault("127.0.0.1:19445");
argPriority.setDefault(800);
// parse all options
optionParser.parse(argc, const_cast<const char **>(argv));
// check if we need to display the usage. exit if we do.
if (argHelp.isSet())
{
optionParser.usage();
return 0;
}
V4L2Grabber grabber(
argDevice.getValue(),
argInput.getValue(),
argVideoStandard.getValue(),
argWidth.getValue(),
argHeight.getValue(),
std::max(0, argCropWidth.getValue()),
std::max(0, argCropHeight.getValue()),
std::max(1, argFrameDecimation.getValue()),
std::max(1, argSizeDecimation.getValue()));
grabber.start();
if (argScreenshot.isSet())
{
grabber.setCallback(&saveScreenshot, nullptr);
grabber.capture(1);
}
else
{
ProtoConnection connection(argAddress.getValue());
grabber.setCallback(&sendImage, &connection);
grabber.capture();
}
grabber.stop();
}
catch (const std::runtime_error & e)
{
// An error occured. Display error and quit
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@ -35,10 +35,6 @@ if (ENABLE_DISPMANX)
add_subdirectory(dispmanx2png)
endif (ENABLE_DISPMANX)
if (ENABLE_V4L2)
add_subdirectory(v4l2_to_png)
endif (ENABLE_V4L2)
add_executable(test_blackborderdetector
TestBlackBorderDetector.cpp)
target_link_libraries(test_blackborderdetector

View File

@ -1,17 +0,0 @@
# find Qt4
find_package(Qt4 REQUIRED QtCore QtGui)
include_directories(${QT_INCLUDES})
add_executable(v4l2_to_png
v4l2_to_png.cpp
V4L2Grabber.h
V4L2Grabber.cpp)
target_link_libraries(v4l2_to_png
getoptPlusPlus)
qt4_use_modules(v4l2_to_png
Core
Gui)

View File

@ -1,104 +0,0 @@
// STL includes
#include <csignal>
#include <iomanip>
// QT includes
#include <QImage>
// getoptPlusPLus includes
#include <getoptPlusPlus/getoptpp.h>
#include "V4L2Grabber.h"
using namespace vlofgren;
/// Data parameter for the video standard
typedef vlofgren::PODParameter<V4L2Grabber::VideoStandard> VideoStandardParameter;
namespace vlofgren {
/// Translates a string (as passed on the commandline) to a color standard
///
/// @param[in] s The string (as passed on the commandline)
/// @return The color standard
/// @throws Parameter::ParameterRejected If the string did not result in a video standard
template<>
V4L2Grabber::VideoStandard VideoStandardParameter::validate(const std::string& s) throw (Parameter::ParameterRejected)
{
QString input = QString::fromStdString(s).toLower();
if (input == "pal")
{
return V4L2Grabber::PAL;
}
else if (input == "ntsc")
{
return V4L2Grabber::NTSC;
}
else if (input == "no-change")
{
return V4L2Grabber::NO_CHANGE;
}
throw Parameter::ParameterRejected("Invalid value for video standard. Valid values are: PAL, NTSC, and NO-CHANGE");
return V4L2Grabber::NO_CHANGE;
}
}
int main(int argc, char** argv)
{
try
{
// create the option parser and initialize all parameters
OptionsParser optionParser("Simple application to send a command to hyperion using the Json interface");
ParameterSet & parameters = optionParser.getParameters();
StringParameter & argDevice = parameters.add<StringParameter> ('d', "device", "The device to use [default=/dev/video0]");
VideoStandardParameter & argVideoStandard = parameters.add<VideoStandardParameter>('v', "video-standard", "The used video standard. Valid values are PAL. NYSC, or NO-CHANGE [default=PAL]");
IntParameter & argInput = parameters.add<IntParameter> ('i', "input", "Input channel [default=0]");
IntParameter & argCropWidth = parameters.add<IntParameter> (0x0, "crop-width", "Number of pixels to crop from the left and right sides in the picture before decimation [default=0]");
IntParameter & argCropHeight = parameters.add<IntParameter> (0x0, "crop-height", "Number of pixels to crop from the top and the bottom in the picture before decimation [default=0]");
IntParameter & argSizeDecimation = parameters.add<IntParameter> ('s', "size-decimator", "Decimation factor for the output size [default=1]");
SwitchParameter<> & argHelp = parameters.add<SwitchParameter<> > ('h', "help", "Show this help message and exit");
// set defaults
argDevice.setDefault("/dev/video0");
argVideoStandard.setDefault(V4L2Grabber::PAL);
argInput.setDefault(0);
argCropWidth.setDefault(0);
argCropHeight.setDefault(0);
argSizeDecimation.setDefault(1);
// parse all options
optionParser.parse(argc, const_cast<const char **>(argv));
// check if we need to display the usage. exit if we do.
if (argHelp.isSet())
{
optionParser.usage();
return 0;
}
V4L2Grabber grabber(
argDevice.getValue(),
argInput.getValue(),
argVideoStandard.getValue(),
std::max(0, argCropWidth.getValue()),
std::max(0, argCropHeight.getValue()),
1,
argSizeDecimation.getValue());
grabber.start();
grabber.capture(1);
grabber.stop();
}
catch (const std::runtime_error & e)
{
// An error occured. Display error and quit
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}