diff --git a/CMakeLists.txt b/CMakeLists.txt index c058526e..1e500bc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,13 @@ if(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) message(FATAL_ERROR "V4L2 grabber requires PROTOBUF. Disable V4L2 or enable PROTOBUF") endif(ENABLE_V4L2 AND NOT ENABLE_PROTOBUF) +option(ENABLE_FB "Enable the framebuffer grabber" OFF) +message(STATUS "ENABLE_FB = " ${ENABLE_FB}) + +if(ENABLE_FB AND ENABLE_DISPMANX) + message(FATAL_ERROR "dispmanx grabber and framebuffer grabber cannot be used at the same time") +endif(ENABLE_FB AND ENABLE_DISPMANX) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code diff --git a/CompileHowto.txt b/CompileHowto.txt index 18f343a7..2ca29f71 100644 --- a/CompileHowto.txt +++ b/CompileHowto.txt @@ -26,6 +26,8 @@ cd "$HYPERION_DIR/build" cmake .. # or if you are not compiling on the raspberry pi and need to disable the Dispmanx grabber and support for spi devices cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_X11=ON .. +# as an alternative for the dispmanx grabber on non-rpi devices (e.g. cubox-i) you could try the framebuffer grabber +cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_FB=ON .. # run make to build Hyperion make diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 3739bba8..ed1e6660 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -17,3 +17,7 @@ // Define to enable PROTOBUF server #cmakedefine ENABLE_PROTOBUF + +// Define to enable the framebuffer grabber +#cmakedefine ENABLE_FB + diff --git a/include/grabber/FramebufferWrapper.h b/include/grabber/FramebufferWrapper.h new file mode 100644 index 00000000..26312c92 --- /dev/null +++ b/include/grabber/FramebufferWrapper.h @@ -0,0 +1,95 @@ +#pragma once + +// QT includes +#include +#include + +// Utils includes +#include +#include +#include +#include +#include + +// Forward class declaration +class FramebufferFrameGrabber; +class Hyperion; +class ImageProcessor; + +/// +/// The FramebufferWrapper uses an instance of the FramebufferFrameGrabber to obtain ImageRgb's from the +/// displayed content. This ImageRgb is processed to a ColorRgb for each led and commmited to the +/// attached Hyperion. +/// +class FramebufferWrapper: public QObject +{ + Q_OBJECT +public: + /// + /// Constructs the framebuffer frame grabber with a specified grab size and update rate. + /// + /// @param[in] device Framebuffer device name/path + /// @param[in] grabWidth The width of the grabbed image [pixels] + /// @param[in] grabHeight The height of the grabbed images [pixels] + /// @param[in] updateRate_Hz The image grab rate [Hz] + /// @param[in] hyperion The instance of Hyperion used to write the led values + /// + FramebufferWrapper(const std::string & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, Hyperion * hyperion); + + /// + /// Destructor of this framebuffer frame grabber. Releases any claimed resources. + /// + virtual ~FramebufferWrapper(); + +public slots: + /// + /// Starts the grabber wich produces led values with the specified update rate + /// + void start(); + + /// + /// Performs a single frame grab and computes the led-colors + /// + void action(); + + /// + /// Stops the grabber + /// + void stop(); + + /// + /// Set the grabbing mode + /// @param[in] mode The new grabbing mode + /// + void setGrabbingMode(const GrabbingMode mode); + + /// + /// Set the video mode (2D/3D) + /// @param[in] mode The new video mode + /// + void setVideoMode(const VideoMode videoMode); + +private: + /// The update rate [Hz] + const int _updateInterval_ms; + /// The timeout of the led colors [ms] + const int _timeout_ms; + /// The priority of the led colors + const int _priority; + + /// The timer for generating events with the specified update rate + QTimer _timer; + + /// The image used for grabbing frames + Image _image; + /// The actual grabber + FramebufferFrameGrabber * _frameGrabber; + /// The processor for transforming images to led colors + ImageProcessor * _processor; + + /// The list with computed led colors + std::vector _ledColors; + + /// Pointer to Hyperion for writing led values + Hyperion * _hyperion; +}; diff --git a/include/utils/Image.h b/include/utils/Image.h index 19b5ae35..0a8ca31a 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -142,7 +142,7 @@ public: /// @param height The height of the image void resize(const unsigned width, const unsigned height) { - if ((width*height) > (_endOfPixels-_pixels)) + if ((width*height) > unsigned((_endOfPixels-_pixels))) { delete[] _pixels; _pixels = new Pixel_T[width*height + 1]; diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index 85279243..e86bee2b 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -2,6 +2,10 @@ if (ENABLE_DISPMANX) add_subdirectory(dispmanx) endif (ENABLE_DISPMANX) +if (ENABLE_FB) + add_subdirectory(framebuffer) +endif (ENABLE_FB) + if (ENABLE_V4L2) add_subdirectory(v4l2) endif (ENABLE_V4L2) diff --git a/libsrc/grabber/framebuffer/CMakeLists.txt b/libsrc/grabber/framebuffer/CMakeLists.txt new file mode 100644 index 00000000..9a6a12be --- /dev/null +++ b/libsrc/grabber/framebuffer/CMakeLists.txt @@ -0,0 +1,35 @@ + +# Find the BCM-package (VC control) +# find_package(BCM REQUIRED) +# include_directories(${BCM_INCLUDE_DIRS}) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/framebuffer) + +# Group the headers that go through the MOC compiler +SET(FramebufferGrabberQT_HEADERS + ${CURRENT_HEADER_DIR}/FramebufferWrapper.h +) + +SET(FramebufferGrabberHEADERS + ${CURRENT_SOURCE_DIR}/FramebufferFrameGrabber.h +) + +SET(FramebufferGrabberSOURCES + ${CURRENT_SOURCE_DIR}/FramebufferWrapper.cpp + ${CURRENT_SOURCE_DIR}/FramebufferFrameGrabber.cpp +) + +QT4_WRAP_CPP(FramebufferGrabberHEADERS_MOC ${FramebufferGrabberQT_HEADERS}) + +add_library(framebuffer-grabber + ${FramebufferGrabberHEADERS} + ${FramebufferGrabberQT_HEADERS} + ${FramebufferGrabberHEADERS_MOC} + ${FramebufferGrabberSOURCES} +) + +target_link_libraries(framebuffer-grabber + hyperion + ${QT_LIBRARIES}) diff --git a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp new file mode 100755 index 00000000..3ab79ce1 --- /dev/null +++ b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// STL includes +#include +#include + +// Local includes +#include "FramebufferFrameGrabber.h" + +FramebufferFrameGrabber::FramebufferFrameGrabber(const std::string & device, const unsigned width, const unsigned height) : + _fbfd(0), + _fbp(0), + _fbDevice(device), + _width(width), + _height(height), + _xScale(1), + _yScale(1) +{ + int result; + struct fb_var_screeninfo vinfo; + + // Check if the framebuffer device can be opened and display the current resolution + _fbfd = open(_fbDevice.c_str(), O_RDONLY); + assert(_fbfd > 0); + + // get variable screen information + result = ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); + assert(result == 0); + + std::cout << "Framebuffer opened with resolution: " << vinfo.xres << "x" << vinfo.yres << "@" << vinfo.bits_per_pixel << "bit" << std::endl; + + close(_fbfd); +} + +FramebufferFrameGrabber::~FramebufferFrameGrabber() +{ +} + +void FramebufferFrameGrabber::setVideoMode(const VideoMode videoMode) +{ + switch (videoMode) { + case VIDEO_3DSBS: + _xScale = 2; + _yScale = 1; + break; + case VIDEO_3DTAB: + _xScale = 1; + _yScale = 2; + break; + case VIDEO_2D: + default: + _xScale = 1; + _yScale = 1; + break; + } +} + +void FramebufferFrameGrabber::grabFrame(Image & image) +{ + struct fb_var_screeninfo vinfo; + unsigned capSize, px, py, index, bytesPerPixel; + float x_scale, y_scale; + + /* resize the given image if needed */ + if (image.width() != _width || image.height() != _height) + { + image.resize(_width, _height); + } + + /* Open the framebuffer device */ + _fbfd = open(_fbDevice.c_str(), O_RDONLY); + + /* get variable screen information */ + ioctl (_fbfd, FBIOGET_VSCREENINFO, &vinfo); + + bytesPerPixel = vinfo.bits_per_pixel / 8; + capSize = vinfo.xres * vinfo.yres * bytesPerPixel; + + /* map the device to memory */ + _fbp = (unsigned char*)mmap(0, capSize, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, _fbfd, 0); + + /* nearest neighbor downscaling */ + x_scale = (vinfo.xres / float(_xScale)) / float(_width); + y_scale = (vinfo.yres / float(_yScale)) / float(_height); + + ColorRgba *pPixel = image.memptr(); + for (unsigned i=0; i < _height; i++) { + for (unsigned j=0; j < _width; j++) { + px = floor(j * x_scale); + py = floor(i * y_scale); + index = (py * vinfo.xres + px) * bytesPerPixel; + + if (vinfo.bits_per_pixel == 16) { + pPixel->blue = (_fbp[index] & 0x1f) << 3; + pPixel->green = (((_fbp[index + 1] & 0x7) << 3) | (_fbp[index] & 0xE0) >> 5) << 2; + pPixel->red = (_fbp[index + 1] & 0xF8); + pPixel->alpha = 255; + } else if(vinfo.bits_per_pixel == 24) { + pPixel->blue = _fbp[index]; + pPixel->green = _fbp[index + 1]; + pPixel->red = _fbp[index + 2]; + pPixel->alpha = 255; + } else if(vinfo.bits_per_pixel == 32) { + pPixel->blue = _fbp[index]; + pPixel->green = _fbp[index + 1]; + pPixel->red = _fbp[index + 2]; + pPixel->alpha = _fbp[index + 3]; + } + + pPixel++; + } + } + + munmap(_fbp, capSize); + close(_fbfd); +} diff --git a/libsrc/grabber/framebuffer/FramebufferFrameGrabber.h b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.h new file mode 100644 index 00000000..d6fe62e4 --- /dev/null +++ b/libsrc/grabber/framebuffer/FramebufferFrameGrabber.h @@ -0,0 +1,61 @@ +#pragma once + +// Utils includes +#include +#include +#include + +/// +/// The FramebufferFrameGrabber is used for creating snapshots of the display (screenshots) +/// +class FramebufferFrameGrabber +{ +public: + /// + /// Construct a FramebufferFrameGrabber that will capture snapshots with specified dimensions. + /// + /// @param[in] device The framebuffer device name/path + /// @param[in] width The width of the captured screenshot + /// @param[in] height The heigth of the captured screenshot + /// + FramebufferFrameGrabber(const std::string & device, const unsigned width, const unsigned height); + ~FramebufferFrameGrabber(); + + /// + /// Set the video mode (2D/3D) + /// @param[in] mode The new video mode + /// + void setVideoMode(const VideoMode videoMode); + + /// + /// Captures a single snapshot of the display and writes the data to the given image. The + /// provided image should have the same dimensions as the configured values (_width and + /// _height) + /// + /// @param[out] image The snapped screenshot (should be initialized with correct width and + /// height) + /// + void grabFrame(Image & image); + +private: + /// Framebuffer file descriptor + int _fbfd; + + /// Pointer to framebuffer + unsigned char * _fbp; + + /// Framebuffer device e.g. /dev/fb0 + const std::string _fbDevice; + + /// With of the captured snapshot [pixels] + const unsigned _width; + + /// Height of the captured snapshot [pixels] + const unsigned _height; + + /// width scale factor for 3D image processing + float _xScale; + + /// height scale factor for 3D image processing + float _yScale; +}; diff --git a/libsrc/grabber/framebuffer/FramebufferWrapper.cpp b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp new file mode 100644 index 00000000..abc6400c --- /dev/null +++ b/libsrc/grabber/framebuffer/FramebufferWrapper.cpp @@ -0,0 +1,79 @@ +// Hyperion includes +#include +#include +#include + +// Framebuffer grabber includes +#include +#include "FramebufferFrameGrabber.h" + +FramebufferWrapper::FramebufferWrapper(const std::string & device, const unsigned grabWidth, const unsigned grabHeight, const unsigned updateRate_Hz, Hyperion * hyperion) : + _updateInterval_ms(1000/updateRate_Hz), + _timeout_ms(2 * _updateInterval_ms), + _priority(1000), + _timer(), + _image(grabWidth, grabHeight), + _frameGrabber(new FramebufferFrameGrabber(device, grabWidth, grabHeight)), + _processor(ImageProcessorFactory::getInstance().newImageProcessor()), + _ledColors(hyperion->getLedCount(), ColorRgb{0,0,0}), + _hyperion(hyperion) +{ + // Configure the timer to generate events every n milliseconds + _timer.setInterval(_updateInterval_ms); + _timer.setSingleShot(false); + + _processor->setSize(grabWidth, grabHeight); + + // Connect the QTimer to this + QObject::connect(&_timer, SIGNAL(timeout()), this, SLOT(action())); +} + +FramebufferWrapper::~FramebufferWrapper() +{ + // Cleanup used resources (ImageProcessor and FrameGrabber) + delete _processor; + delete _frameGrabber; +} + +void FramebufferWrapper::start() +{ + // Start the timer with the pre configured interval + _timer.start(); +} + +void FramebufferWrapper::action() +{ + // Grab frame into the allocated image + _frameGrabber->grabFrame(_image); + + _processor->process(_image, _ledColors); + + _hyperion->setColors(_priority, _ledColors, _timeout_ms); +} +void FramebufferWrapper::stop() +{ + // Stop the timer, effectivly stopping the process + _timer.stop(); +} + +void FramebufferWrapper::setGrabbingMode(const GrabbingMode mode) +{ + switch (mode) + { + case GRABBINGMODE_VIDEO: + case GRABBINGMODE_AUDIO: + case GRABBINGMODE_PHOTO: + case GRABBINGMODE_MENU: + case GRABBINGMODE_INVALID: + start(); + break; + case GRABBINGMODE_OFF: + stop(); + break; + } +} + +void FramebufferWrapper::setVideoMode(const VideoMode mode) +{ + _frameGrabber->setVideoMode(mode); +} diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index 49df4bfe..d3c859fb 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -14,6 +14,10 @@ if (ENABLE_DISPMANX) target_link_libraries(hyperiond dispmanx-grabber) endif (ENABLE_DISPMANX) +if (ENABLE_FB) + target_link_libraries(hyperiond framebuffer-grabber) +endif (ENABLE_FB) + if (ENABLE_V4L2) target_link_libraries(hyperiond v4l2-grabber) endif (ENABLE_V4L2) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 4c21b39b..cf65db9a 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -1,7 +1,6 @@ // C++ includes #include #include -#include // QT includes #include @@ -27,6 +26,11 @@ #include #endif +#ifdef ENABLE_FB +// Framebuffer grabber includes +#include +#endif + // XBMC Video checker includes #include @@ -183,11 +187,13 @@ int main(int argc, char** argv) std::cout << "Frame grabber created and started" << std::endl; } #else +#ifndef ENABLE_FB if (config.isMember("framegrabber")) { std::cerr << "The dispmanx framegrabber can not be instantiated, becuse it has been left out from the build" << std::endl; } #endif +#endif #ifdef ENABLE_V4L2 // construct and start the v4l2 grabber if the configuration is present @@ -225,6 +231,38 @@ int main(int argc, char** argv) std::cerr << "The v4l2 grabber can not be instantiated, becuse it has been left out from the build" << std::endl; } #endif + +#ifdef ENABLE_FB + // Construct and start the framebuffer grabber if the configuration is present + FramebufferWrapper * fbGrabber = nullptr; + if (config.isMember("framegrabber")) + { + const Json::Value & grabberConfig = config["framegrabber"]; + // TODO get device from config + fbGrabber = new FramebufferWrapper( + grabberConfig.get("device", "/dev/fb0").asString(), + grabberConfig["width"].asUInt(), + grabberConfig["height"].asUInt(), + grabberConfig["frequency_Hz"].asUInt(), + &hyperion); + + if (xbmcVideoChecker != nullptr) + { + QObject::connect(xbmcVideoChecker, SIGNAL(grabbingMode(GrabbingMode)), fbGrabber, SLOT(setGrabbingMode(GrabbingMode))); + QObject::connect(xbmcVideoChecker, SIGNAL(videoMode(VideoMode)), fbGrabber, SLOT(setVideoMode(VideoMode))); + } + + fbGrabber->start(); + std::cout << "Framebuffer grabber created and started" << std::endl; + } +#else +#ifndef ENABLE_DISPMANX + if (config.isMember("framegrabber")) + { + std::cerr << "The framebuffer grabber can not be instantiated, becuse it has been left out from the build" << std::endl; + } +#endif +#endif // Create Json server if configuration is present JsonServer * jsonServer = nullptr; @@ -263,6 +301,9 @@ int main(int argc, char** argv) #ifdef ENABLE_DISPMANX delete dispmanx; #endif +#ifdef ENABLE_FB + delete fbGrabber; +#endif #ifdef ENABLE_V4L2 delete v4l2Grabber; #endif