diff --git a/.gitignore b/.gitignore index 550dcb07..91a498c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /*.user -/build -/build-x86 +/build* .DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..aa392190 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dependencies/external/protobuf"] + path = dependencies/external/protobuf + url = https://github.com/tvdzwan/protobuf.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f4ff4f6..c058526e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,14 +11,27 @@ option(ENABLE_DISPMANX "Enable the RPi dispmanx grabber" ON) message(STATUS "ENABLE_DISPMANX = " ${ENABLE_DISPMANX}) option(ENABLE_SPIDEV "Enable the SPIDEV device" ON) -message(STATUS "ENABLE_SPIDEV = " ${ENABLE_SPIDEV}) +message(STATUS "ENABLE_SPIDEV = " ${ENABLE_SPIDEV}) + +option(ENABLE_WS2812BPWM "Enable the WS2812b-PWM device" OFF) +message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM}) option(ENABLE_V4L2 "Enable the V4L2 grabber" ON) message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2}) +option(ENABLE_X11 "Enable the X11 grabber" OFF) +message(STATUS "ENABLE_X11 = " ${ENABLE_X11}) + option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON) message(STATUS "ENABLE_TINKERFORGE = " ${ENABLE_TINKERFORGE}) +option(ENABLE_PROTOBUF "Enable PROTOBUF server" ON) +message(STATUS "ENABLE_PROTOBUF = " ${ENABLE_PROTOBUF}) + +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) + # Createt the configuration file # configure a header file to pass some of the CMake settings # to the source code @@ -44,8 +57,8 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # Prefer static linking over dynamic #set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") -#set(CMAKE_BUILD_TYPE "Debug") -set(CMAKE_BUILD_TYPE "Release") +set(CMAKE_BUILD_TYPE "Debug") +#set(CMAKE_BUILD_TYPE "Release") # enable C++11 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall") @@ -54,13 +67,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall") # Configure the use of QT4 find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED QUIET) -# add protocol buffers (make sure to find the static version) -set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD ${CMAKE_FIND_LIBRARY_SUFFIXES}) -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") -find_package(Protobuf REQUIRED) -set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_OLD}) -set(CMAKE_FIND_LIBRARY_SUFFIXES_OLD) - #add libusb and pthreads find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) diff --git a/CompileHowto.txt b/CompileHowto.txt index 17a274e3..7ad606aa 100644 --- a/CompileHowto.txt +++ b/CompileHowto.txt @@ -1,6 +1,6 @@ # Install the required tools and dependencies sudo apt-get update -sudo apt-get install git cmake build-essential libprotobuf-dev libQt4-dev libusb-1.0-0-dev protobuf-compiler python-dev +sudo apt-get install git cmake build-essential libQt4-dev libusb-1.0-0-dev python-dev # RPI ONLY: when you build on the rapberry pi and inlcude the dispmanx grabber (which is the default) # you also need the firmware including headers installed. This downloads the firmware from the raspberrypi github @@ -11,7 +11,7 @@ sudo cp -R "$FIRMWARE_DIR/hardfp/opt/*" /opt # create hyperion directory and checkout the code from github export HYPERION_DIR="hyperion" -git clone https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR" +git clone --recursive https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR" # create and enter the build directory mkdir "$HYPERION_DIR/build" @@ -20,7 +20,7 @@ cd "$HYPERION_DIR/build" # run cmake to generate make files on the rsapberry pi 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 .. +cmake -DENABLE_DISPMANX=OFF -DENABLE_SPIDEV=OFF -DENABLE_X11=ON .. # run make to build Hyperion make diff --git a/CrossCompileHowto.txt b/CrossCompileHowto.txt index 8f50445f..ca6df73f 100644 --- a/CrossCompileHowto.txt +++ b/CrossCompileHowto.txt @@ -1,6 +1,6 @@ ON RASPBERRY -------------- -sudo apt-get install libprotobuf-dev libQt4-dev libusb-1.0-0-dev python-dev rsync +sudo apt-get install libQt4-dev libusb-1.0-0-dev python-dev rsync ON HOST --------- @@ -8,24 +8,36 @@ export RASPI=192.168.1.17 export RASCROSS_DIR="$HOME/raspberrypi" export HYPERION_DIR="$HOME/hyperion" +# install required packages +sudo apt-get install git rsync cmake build-essential libQt4-dev libusb-1.0-0-dev python-dev sudo apt-get install git rsync cmake ia32-libs protobuf-compiler + # On newer version of Ubuntu (and maybe other distros) the ia32-libs is not available, the following # install solved this for me (TODO: verify what is really required) sudo apt-get install libc6:i386 libgcc1:i386 gcc-4.6-base:i386 libstdc++5:i386 libstdc++6:i386 +# create the rootfs by copying it from an rpi running for example raspbmc mkdir -p "$RASCROSS_DIR/rootfs" -git clone git://github.com/raspberrypi/tools.git "$RASCROSS_DIR/tools" - -git clone https://github.com/raspberrypi/firmware.git "$RASCROSS_DIR/firmware" -ln -s "$RASCROSS_DIR/firmware/opt" "$RASCROSS_DIR/rootfs/opt" - rsync -rl --delete-after --safe-links pi@$RASPI:/{lib,usr} "$RASCROSS_DIR/rootfs" -git clone https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR" +# get the raspberry pi firmware and add it to the rootfs +git clone https://github.com/raspberrypi/firmware.git "$RASCROSS_DIR/firmware" +ln -s "$RASCROSS_DIR/firmware/hardfp/opt" "$RASCROSS_DIR/rootfs/opt" + +# get the compile tools +git clone git://github.com/raspberrypi/tools.git "$RASCROSS_DIR/tools" + +# get the Hyperion sources +git clone --recursive https://github.com/tvdzwan/hyperion.git "$HYPERION_DIR" + +# do a native build (to build the protobuf compiler for the native platform) mkdir "$HYPERION_DIR/build" -cmake -DCMAKE_TOOLCHAIN_FILE="$HYPERION_DIR/Toolchain-RaspberryPi.cmake" --build "$HYPERION_DIR/build" "$HYPERION_DIR" - +cmake -DENABLE_DISPMANX=OFF --build "$HYPERION_DIR/build" "$HYPERION_DIR" +# do the rpi build +# specify the protoc export file to import the protobuf compiler from the native build +mkdir "$HYPERION_DIR/build-rpi" +cmake -DCMAKE_TOOLCHAIN_FILE="$HYPERION_DIR/Toolchain-RaspberryPi.cmake" -DIMPORT_PROTOC=$HYPERION_DIR/build/protoc_export.cmake --build "$HYPERION_DIR/build-rpi" "$HYPERION_DIR" ------------------------------------------------------------------------------ These instructions are based on the guide given by: diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 17f040c0..3739bba8 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -9,5 +9,11 @@ // Define to enable the spi-device #cmakedefine ENABLE_SPIDEV +// Define to enable the ws2812b-pwm-device +#cmakedefine ENABLE_WS2812BPWM + // Define to enable the spi-device #cmakedefine ENABLE_TINKERFORGE + +// Define to enable PROTOBUF server +#cmakedefine ENABLE_PROTOBUF diff --git a/Toolchain-RaspberryPi.cmake b/Toolchain-RaspberryPi.cmake index 77ea25b0..1be297d4 100644 --- a/Toolchain-RaspberryPi.cmake +++ b/Toolchain-RaspberryPi.cmake @@ -4,8 +4,8 @@ SET(CMAKE_SYSTEM_NAME Linux) SET(CMAKE_SYSTEM_VERSION 1) # specify the cross compiler -SET(CMAKE_C_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-gcc) -SET(CMAKE_CXX_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-g++) +SET(CMAKE_C_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc) +SET(CMAKE_CXX_COMPILER ${RASPCROSS_DIR}/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-g++) # where is the target environment SET(CMAKE_FIND_ROOT_PATH ${RASPCROSS_DIR}/rootfs) diff --git a/cmake/Findlibusb-1.0.cmake b/cmake/Findlibusb-1.0.cmake index 26a82c4f..77474e9a 100644 --- a/cmake/Findlibusb-1.0.cmake +++ b/cmake/Findlibusb-1.0.cmake @@ -87,6 +87,7 @@ else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}") endif (NOT libusb_1_FIND_QUIETLY) else (LIBUSB_1_FOUND) + unset(LIBUSB_1_LIBRARY CACHE) if (libusb_1_FIND_REQUIRED) message(FATAL_ERROR "Could not find libusb") endif (libusb_1_FIND_REQUIRED) diff --git a/cmake/qt4/Qt4ConfigDependentSettings.cmake b/cmake/qt4/Qt4ConfigDependentSettings.cmake index 6db5da18..6df28f47 100644 --- a/cmake/qt4/Qt4ConfigDependentSettings.cmake +++ b/cmake/qt4/Qt4ConfigDependentSettings.cmake @@ -270,6 +270,16 @@ if(Q_WS_X11) endif() +if(Q_WS_QWS) + set(CMAKE_THREAD_PREFER_PTHREADS 1) + find_package(Threads) + if(CMAKE_USE_PTHREADS_INIT) + set(QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_THREAD_LIBS_INIT}) + endif() + + set (QT_QTCORE_LIB_DEPENDENCIES ${QT_QTCORE_LIB_DEPENDENCIES} ${CMAKE_DL_LIBS}) + +endif() if(Q_WS_WIN) set(QT_QTGUI_LIB_DEPENDENCIES ${QT_QTGUI_LIB_DEPENDENCIES} imm32 winmm) diff --git a/config/hyperion.config.json b/config/hyperion.config.json index fa08d80d..8e361ed0 100644 --- a/config/hyperion.config.json +++ b/config/hyperion.config.json @@ -80,7 +80,8 @@ { "type" : "none", "time_ms" : 200, - "updateFrequency" : 20.0000 + "updateFrequency" : 20.0000, + "updateDelay" : 0 } }, diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 059e258e..17747bd5 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -1,2 +1,103 @@ +add_subdirectory(build/getoptPlusPlus) +add_subdirectory(build/hidapi) +add_subdirectory(build/jsoncpp) +add_subdirectory(build/serial) +add_subdirectory(build/tinkerforge) -add_subdirectory(build) +if(ENABLE_PROTOBUF) + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared protobuf library") + add_subdirectory(external/protobuf) + + if(CMAKE_CROSSCOMPILING) + # when crosscompiling import the protoc executable targets from a file generated by a native build + option(IMPORT_PROTOC "Protoc export file (protoc_export.cmake) from a native build" "IMPORT_PROTOC-FILE_NOT_FOUND") + include(${IMPORT_PROTOC}) + else() + # export the protoc compiler so it can be used when cross compiling + export(TARGETS protoc_compiler FILE "${CMAKE_BINARY_DIR}/protoc_export.cmake") + endif() + + # define the include for the protobuf library at the parent scope + set(PROTOBUF_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/protobuf/src") + set(PROTOBUF_INCLUDE_DIRS ${PROTOBUF_INCLUDE_DIRS} PARENT_SCOPE) + + # define the protoc executable at the parent scope + get_property(PROTOBUF_PROTOC_EXECUTABLE TARGET protoc_compiler PROPERTY LOCATION) + set(PROTOBUF_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE} PARENT_SCOPE) + message(STATUS "Using protobuf compiler: " ${PROTOBUF_PROTOC_EXECUTABLE}) + + #============================================================================= + # Copyright 2009 Kitware, Inc. + # Copyright 2009-2011 Philip Lowman + # Copyright 2008 Esben Mose Hansen, Ange Optimization ApS + # + # Distributed under the OSI-approved BSD License (the "License"); + # see accompanying file Copyright.txt for details. + # + # This software is distributed WITHOUT ANY WARRANTY; without even the + # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + # See the License for more information. + #============================================================================= + # (To distribute this file outside of CMake, substitute the full + # License text for the above reference.) + function(PROTOBUF_GENERATE_CPP SRCS HDRS) + if(NOT ARGN) + message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files") + return() + endif() + + if(PROTOBUF_GENERATE_CPP_APPEND_PATH) + # Create an include path for each file specified + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(ABS_PATH ${ABS_FIL} PATH) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + else() + set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(DEFINED PROTOBUF_IMPORT_DIRS) + foreach(DIR ${PROTOBUF_IMPORT_DIRS}) + get_filename_component(ABS_PATH ${DIR} ABSOLUTE) + list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) + if(${_contains_already} EQUAL -1) + list(APPEND _protobuf_include_path -I ${ABS_PATH}) + endif() + endforeach() + endif() + + if(CMAKE_CROSSCOMPILING) + set(PROTOC_DEPENDENCY ${PROTOBUF_PROTOC_EXECUTABLE}) + else() + set(PROTOC_DEPENDENCY protoc_compiler) + endif() + + set(${SRCS}) + set(${HDRS}) + foreach(FIL ${ARGN}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") + list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL} + DEPENDS ${ABS_FIL} ${PROTOC_DEPENDENCY} + COMMENT "Running C++ protocol buffer compiler on ${FIL}" + VERBATIM + ) + endforeach() + + set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} PARENT_SCOPE) + endfunction() +endif() diff --git a/dependencies/build/CMakeLists.txt b/dependencies/build/CMakeLists.txt deleted file mode 100644 index e931ea79..00000000 --- a/dependencies/build/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ - -add_subdirectory(getoptPlusPlus) -add_subdirectory(hidapi) -add_subdirectory(jsoncpp) -add_subdirectory(serial) -add_subdirectory(tinkerforge) diff --git a/dependencies/build/getoptPlusPlus/getoptpp.cc b/dependencies/build/getoptPlusPlus/getoptpp.cc index 5444a325..37212901 100644 --- a/dependencies/build/getoptPlusPlus/getoptpp.cc +++ b/dependencies/build/getoptPlusPlus/getoptpp.cc @@ -17,6 +17,9 @@ #include "getoptpp.h" #include #include +#include +#include +#include using namespace std; @@ -34,107 +37,124 @@ OptionsParser::OptionsParser(const char* programDesc) : fprogramDesc(programDesc OptionsParser::~OptionsParser() {} ParameterSet& OptionsParser::getParameters() { - return parameters; + return parameters; } void OptionsParser::parse(int argc, const char* argv[]) throw(runtime_error) { - argv0 = argv[0]; + argv0 = argv[0]; - if(argc == 1) return; + if(argc == 1) return; - vector v(&argv[1], &argv[argc]); + vector v(&argv[1], &argv[argc]); - ParserState state(/* *this,*/ v); + ParserState state(/* *this,*/ v); - for(; !state.end(); state.advance()) { + for(; !state.end(); state.advance()) { - std::list::iterator i; + std::list::iterator i; - for(i = parameters.parameters.begin(); - i != parameters.parameters.end(); i++) - { - int n = 0; - try - { - n = (*i)->receive(state); - } - catch(Parameter::ExpectedArgument &) - { - throw Parameter::ExpectedArgument(state.get() + ": expected an argument"); - } - catch(Parameter::UnexpectedArgument &) - { - throw Parameter::UnexpectedArgument(state.get() + ": did not expect an argument"); - } - catch(Switchable::SwitchingError &) - { - throw Parameter::ParameterRejected(state.get() + ": parameter already set"); - } - catch(Parameter::ParameterRejected & pr) { - std::string what = pr.what(); - if(what.length()) - { - throw Parameter::ParameterRejected(state.get() + ": " + what); - } - throw Parameter::ParameterRejected(state.get() + " (unspecified error)"); - } + for(i = parameters.parameters.begin(); + i != parameters.parameters.end(); i++) + { + int n = 0; + try + { + n = (*i)->receive(state); + } + catch(Parameter::ExpectedArgument &) + { + throw Parameter::ExpectedArgument(state.get() + ": expected an argument"); + } + catch(Parameter::UnexpectedArgument &) + { + throw Parameter::UnexpectedArgument(state.get() + ": did not expect an argument"); + } + catch(Switchable::SwitchingError &) + { + throw Parameter::ParameterRejected(state.get() + ": parameter already set"); + } + catch(Parameter::ParameterRejected & pr) { + std::string what = pr.what(); + if(what.length()) + { + throw Parameter::ParameterRejected(state.get() + ": " + what); + } + throw Parameter::ParameterRejected(state.get() + " (unspecified error)"); + } - for (int j = 1; j < n; ++j) - { - state.advance(); - } + for (int j = 1; j < n; ++j) + { + state.advance(); + } - if(n != 0) - { - break; - } - } + if(n != 0) + { + break; + } + } - if(i == parameters.parameters.end()) { - std::string file = state.get(); - if(file == "--") { - state.advance(); - break; - } - else if(file.at(0) == '-') - throw Parameter::ParameterRejected(string("Bad parameter: ") + file); - else files.push_back(state.get()); - } - } + if(i == parameters.parameters.end()) { + std::string file = state.get(); + if(file == "--") { + state.advance(); + break; + } + else if(file.at(0) == '-') + throw Parameter::ParameterRejected(string("Bad parameter: ") + file); + else files.push_back(state.get()); + } + } - if(!state.end()) for(; !state.end(); state.advance()) { - files.push_back(state.get()); - } + if(!state.end()) for(; !state.end(); state.advance()) { + files.push_back(state.get()); + } } void OptionsParser::usage() const { - cerr << fprogramDesc << endl; - cerr << "Build time: " << __DATE__ << " " << __TIME__ << endl << endl; - cerr << "Usage: " << programName() << " [OPTIONS]" << endl << endl; + cerr << fprogramDesc << endl; + cerr << "Build time: " << __DATE__ << " " << __TIME__ << endl << endl; + cerr << "Usage: " << programName() << " [OPTIONS]" << endl << endl; - cerr << "Parameters: " << endl; + cerr << "Parameters: " << endl; - std::list::const_iterator i; - for(i = parameters.parameters.begin(); - i != parameters.parameters.end(); i++) - { - cerr.width(33); - cerr << std::left << " " + (*i)->usageLine(); + int totalWidth = 80; + int usageWidth = 33; - cerr.width(40); - cerr << std::left << (*i)->description() << endl; + // read total width from the terminal + struct winsize w; + if (ioctl(0, TIOCGWINSZ, &w) == 0) + { + if (w.ws_col > totalWidth) + totalWidth = w.ws_col; + } - } + std::list::const_iterator i; + for(i = parameters.parameters.begin(); + i != parameters.parameters.end(); i++) + { + cerr.width(usageWidth); + cerr << std::left << " " + (*i)->usageLine(); + + std::string description = (*i)->description(); + while (int(description.length()) > (totalWidth - usageWidth)) + { + size_t pos = description.find_last_of(' ', totalWidth - usageWidth); + cerr << description.substr(0, pos) << std::endl << std::string(usageWidth - 1, ' '); + description = description.substr(pos); + } + cerr << description << endl; + + } } const vector& OptionsParser::getFiles() const { - return files; + return files; } const string& OptionsParser::programName() const { - return argv0; + return argv0; } /* @@ -144,15 +164,15 @@ const string& OptionsParser::programName() const { */ ParameterSet::ParameterSet(const ParameterSet& ps) { - throw new runtime_error("ParameterSet not copyable"); + throw new runtime_error("ParameterSet not copyable"); } ParameterSet::~ParameterSet() { - for(std::list::iterator i = parameters.begin(); - i != parameters.end(); i++) - { - delete *i; - } + for(std::list::iterator i = parameters.begin(); + i != parameters.end(); i++) + { + delete *i; + } } @@ -161,18 +181,18 @@ ParameterSet::~ParameterSet() { */ Parameter& ParameterSet::operator[](char c) const { - for(std::list::const_iterator i = parameters.begin(); i!= parameters.end(); i++) { - if((*i)->shortOption() == c) return *(*i); - } - throw out_of_range("ParameterSet["+string(&c)+string("]")); + for(std::list::const_iterator i = parameters.begin(); i!= parameters.end(); i++) { + if((*i)->shortOption() == c) return *(*i); + } + throw out_of_range("ParameterSet["+string(&c)+string("]")); } Parameter& ParameterSet::operator[](const string& param) const { - for(std::list::const_iterator i = parameters.begin(); i!= parameters.end(); i++) { - if((*i)->longOption() == param) return *(*i); - } - throw out_of_range("ParameterSet["+param+"]"); + for(std::list::const_iterator i = parameters.begin(); i!= parameters.end(); i++) { + if((*i)->longOption() == param) return *(*i); + } + throw out_of_range("ParameterSet["+param+"]"); } @@ -186,29 +206,29 @@ Parameter& ParameterSet::operator[](const string& param) const { ParserState::ParserState(/*OptionsParser &opts, */vector& args) : - /*opts(opts),*/ arguments(args), iterator(args.begin()) + /*opts(opts),*/ arguments(args), iterator(args.begin()) { } const string ParserState::peek() const { - vector::const_iterator next = iterator+1; - if(next != arguments.end()) return *next; - else return ""; + vector::const_iterator next = iterator+1; + if(next != arguments.end()) return *next; + else return ""; } const string ParserState::get() const { - if(!end()) return *iterator; - else return ""; + if(!end()) return *iterator; + else return ""; } void ParserState::advance() { - iterator++; + iterator++; } bool ParserState::end() const { - return iterator == arguments.end(); + return iterator == arguments.end(); } @@ -222,7 +242,7 @@ bool ParserState::end() const { Parameter::Parameter(char shortOption, const std::string & longOption, const std::string & description) : - fshortOption(shortOption), flongOption(longOption), fdescription(description) + fshortOption(shortOption), flongOption(longOption), fdescription(description) { } @@ -250,22 +270,22 @@ MultiSwitchable::~MultiSwitchable() {} void UniquelySwitchable::set() throw (Switchable::SwitchingError) { - if(UniquelySwitchable::isSet()) throw Switchable::SwitchingError(); - fset = true; + if(UniquelySwitchable::isSet()) throw Switchable::SwitchingError(); + fset = true; } UniquelySwitchable::~UniquelySwitchable() {} PresettableUniquelySwitchable::~PresettableUniquelySwitchable() {} bool PresettableUniquelySwitchable::isSet() const { - return UniquelySwitchable::isSet() || fpreset.isSet(); + return UniquelySwitchable::isSet() || fpreset.isSet(); } void PresettableUniquelySwitchable::set() throw (Switchable::SwitchingError) { - UniquelySwitchable::set(); + UniquelySwitchable::set(); } void PresettableUniquelySwitchable::preset() { - fpreset.set(); + fpreset.set(); } /* @@ -279,58 +299,58 @@ void PresettableUniquelySwitchable::preset() { template<> PODParameter::PODParameter(char shortOption, const char *longOption, - const char* description) : CommonParameter(shortOption, longOption, description) { + const char* description) : CommonParameter(shortOption, longOption, description) { } template<> int PODParameter::validate(const string &s) throw(Parameter::ParameterRejected) { - // This is sadly necessary for strto*-functions to operate on - // const char*. The function doesn't write to the memory, though, - // so it's quite safe. + // This is sadly necessary for strto*-functions to operate on + // const char*. The function doesn't write to the memory, though, + // so it's quite safe. - char* cstr = const_cast(s.c_str()); - if(*cstr == '\0') throw ParameterRejected("No argument given"); + char* cstr = const_cast(s.c_str()); + if(*cstr == '\0') throw ParameterRejected("No argument given"); - long l = strtol(cstr, &cstr, 10); - if(*cstr != '\0') throw ParameterRejected("Expected int"); + long l = strtol(cstr, &cstr, 10); + if(*cstr != '\0') throw ParameterRejected("Expected int"); - if(l > INT_MAX || l < INT_MIN) { - throw ParameterRejected("Expected int"); - } + if(l > INT_MAX || l < INT_MIN) { + throw ParameterRejected("Expected int"); + } - return l; + return l; } template<> long PODParameter::validate(const string &s) throw(Parameter::ParameterRejected) { - char* cstr = const_cast(s.c_str()); - if(*cstr == '\0') throw ParameterRejected("No argument given"); + char* cstr = const_cast(s.c_str()); + if(*cstr == '\0') throw ParameterRejected("No argument given"); - long l = strtol(cstr, &cstr, 10); - if(*cstr != '\0') throw ParameterRejected("Expected long"); + long l = strtol(cstr, &cstr, 10); + if(*cstr != '\0') throw ParameterRejected("Expected long"); - return l; + return l; } template<> double PODParameter::validate(const string &s) throw(Parameter::ParameterRejected) { - char* cstr = const_cast(s.c_str()); - if(*cstr == '\0') throw ParameterRejected("No argument given"); + char* cstr = const_cast(s.c_str()); + if(*cstr == '\0') throw ParameterRejected("No argument given"); - double d = strtod(cstr, &cstr); - if(*cstr != '\0') throw ParameterRejected("Expected double"); + double d = strtod(cstr, &cstr); + if(*cstr != '\0') throw ParameterRejected("Expected double"); - return d; + return d; } template<> string PODParameter::validate(const string &s) throw(Parameter::ParameterRejected) { - return s; + return s; } diff --git a/dependencies/external/protobuf b/dependencies/external/protobuf new file mode 160000 index 00000000..efb59b79 --- /dev/null +++ b/dependencies/external/protobuf @@ -0,0 +1 @@ +Subproject commit efb59b79e5a8f26eae4d15f38bbfb5667e23df60 diff --git a/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id b/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id new file mode 100644 index 00000000..25e27e13 --- /dev/null +++ b/deploy/hummingboard_prerelease.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +63b2cc4bf190a0c0c996be6db30fcf03109c9625 \ No newline at end of file diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index ab29ef88..05e6311d 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -9546e335179f732ff68ea9bc47020a19e4b6f44c \ No newline at end of file +0598f021ab2b4e585b03034d9451b0559a85f038 \ No newline at end of file diff --git a/doc/datasheets/APA102_LED.pdf b/doc/datasheets/APA102_LED.pdf new file mode 100644 index 00000000..2a457631 Binary files /dev/null and b/doc/datasheets/APA102_LED.pdf differ diff --git a/include/grabber/V4L2Grabber.h b/include/grabber/V4L2Grabber.h index fe867112..c4fc5a27 100644 --- a/include/grabber/V4L2Grabber.h +++ b/include/grabber/V4L2Grabber.h @@ -11,119 +11,115 @@ // util includes #include #include +#include #include +#include // grabber includes #include -#include /// Capture class for V4L2 devices /// /// @see http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html class V4L2Grabber : public QObject { - Q_OBJECT + Q_OBJECT public: - V4L2Grabber(const std::string & device, - int input, - VideoStandard videoStandard, PixelFormat pixelFormat, - int width, - int height, - int frameDecimation, - int horizontalPixelDecimation, - int verticalPixelDecimation); - virtual ~V4L2Grabber(); + V4L2Grabber(const std::string & device, + int input, + VideoStandard videoStandard, PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int horizontalPixelDecimation, + int verticalPixelDecimation); + virtual ~V4L2Grabber(); public slots: - void setCropping(int cropLeft, - int cropRight, - int cropTop, - int cropBottom); + void setCropping(int cropLeft, + int cropRight, + int cropTop, + int cropBottom); - void set3D(VideoMode mode); + void set3D(VideoMode mode); - void setSignalThreshold(double redSignalThreshold, - double greenSignalThreshold, - double blueSignalThreshold, - int noSignalCounterThreshold); + void setSignalThreshold(double redSignalThreshold, + double greenSignalThreshold, + double blueSignalThreshold, + int noSignalCounterThreshold); - void start(); + void start(); - void stop(); + void stop(); signals: - void newFrame(const Image & image); + void newFrame(const Image & image); private slots: - int read_frame(); + int read_frame(); private: - void open_device(); + void open_device(); - void close_device(); + void close_device(); - void init_read(unsigned int buffer_size); + void init_read(unsigned int buffer_size); - void init_mmap(); + void init_mmap(); - void init_userp(unsigned int buffer_size); + void init_userp(unsigned int buffer_size); - void init_device(VideoStandard videoStandard, int input); + void init_device(VideoStandard videoStandard, int input); - void uninit_device(); + void uninit_device(); - void start_capturing(); + void start_capturing(); - void stop_capturing(); + void stop_capturing(); - bool process_image(const void *p, int size); + bool process_image(const void *p, int size); - void process_image(const uint8_t *p); + void process_image(const uint8_t *p); - int xioctl(int request, void *arg); + int xioctl(int request, void *arg); - void throw_exception(const std::string &error); + void throw_exception(const std::string &error); - void throw_errno_exception(const std::string &error); + void throw_errno_exception(const std::string &error); private: - enum io_method { - IO_METHOD_READ, - IO_METHOD_MMAP, - IO_METHOD_USERPTR - }; + enum io_method { + IO_METHOD_READ, + IO_METHOD_MMAP, + IO_METHOD_USERPTR + }; - struct buffer { - void *start; - size_t length; - }; + struct buffer { + void *start; + size_t length; + }; private: - const std::string _deviceName; - const io_method _ioMethod; - int _fileDescriptor; - std::vector _buffers; + const std::string _deviceName; + const io_method _ioMethod; + int _fileDescriptor; + std::vector _buffers; - PixelFormat _pixelFormat; - int _width; - int _height; - int _frameByteSize; - int _cropLeft; - int _cropRight; - int _cropTop; - int _cropBottom; - int _frameDecimation; - int _horizontalPixelDecimation; - int _verticalPixelDecimation; - int _noSignalCounterThreshold; + PixelFormat _pixelFormat; + int _width; + int _height; + int _lineLength; + int _frameByteSize; + int _frameDecimation; + int _noSignalCounterThreshold; - ColorRgb _noSignalThresholdColor; + ColorRgb _noSignalThresholdColor; - VideoMode _mode3D; + int _currentFrame; + int _noSignalCounter; - int _currentFrame; - int _noSignalCounter; + QSocketNotifier * _streamNotifier; - QSocketNotifier * _streamNotifier; + ImageResampler _imageResampler; }; diff --git a/include/grabber/X11Grabber.h b/include/grabber/X11Grabber.h new file mode 100644 index 00000000..ee857b01 --- /dev/null +++ b/include/grabber/X11Grabber.h @@ -0,0 +1,39 @@ + +// Hyperion-utils includes +#include +#include +#include + +// X11 includes +#include + +class X11Grabber +{ +public: + + X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + + virtual ~X11Grabber(); + + int open(); + + Image & grab(); + +private: + ImageResampler _imageResampler; + + int _cropLeft; + int _cropRight; + int _cropTop; + int _cropBottom; + + /// Reference to the X11 display (nullptr if not opened) + Display * _x11Display; + + unsigned _screenWidth; + unsigned _screenHeight; + + Image _image; + + int updateScreenDimensions(); +}; diff --git a/include/protoserver/ProtoConnection.h b/include/protoserver/ProtoConnection.h new file mode 100644 index 00000000..bb41e1b7 --- /dev/null +++ b/include/protoserver/ProtoConnection.h @@ -0,0 +1,102 @@ +#pragma once + +// stl includes +#include + +// Qt includes +#include +#include +#include +#include + +// hyperion util +#include +#include + +// jsoncpp includes +#include + +/// +/// 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(); + + /// Do not read reply messages from Hyperion if set to true + void setSkipReply(bool skip); + + /// + /// Set all leds to the specified color + /// + /// @param color The color + /// @param priority The priority + /// @param duration The duration in milliseconds + /// + void setColor(const ColorRgb & color, int priority, int duration = 1); + + /// + /// 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 & image, int priority, int duration = -1); + + /// + /// Clear the given priority channel + /// + /// @param priority The priority + /// + void clear(int priority); + + /// + /// Clear all priority channels + /// + void clearAll(); + +private: + /// Try to connect to the Hyperion host + void connectToHost(); + + /// + /// Send a command message and receive its reply + /// + /// @param message The message to send + /// + void sendMessage(const proto::HyperionRequest & message); + + /// + /// Parse a reply message + /// + /// @param reply The received reply + /// + /// @return true if the reply indicates success + /// + bool parseReply(const proto::HyperionReply & reply); + +private: + /// The TCP-Socket with the connection to the server + QTcpSocket _socket; + + /// Host address + QString _host; + + /// Host port + uint16_t _port; + + /// Skip receiving reply messages from Hyperion if set + bool _skipReply; +}; diff --git a/include/protoserver/ProtoConnectionWrapper.h b/include/protoserver/ProtoConnectionWrapper.h new file mode 100644 index 00000000..f5b072ea --- /dev/null +++ b/include/protoserver/ProtoConnectionWrapper.h @@ -0,0 +1,34 @@ +// Qt includes +#include + +// hyperion includes +#include +#include + +// hyperion proto includes +#include "protoserver/ProtoConnection.h" + +/// This class handles callbacks from the V4L2 grabber +class ProtoConnectionWrapper : public QObject +{ + Q_OBJECT + +public: + ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply); + virtual ~ProtoConnectionWrapper(); + +public slots: + /// Handle a single image + /// @param image The image to process + void receiveImage(const Image & image); + +private: + /// Priority for calls to Hyperion + const int _priority; + + /// Duration for color calls to Hyperion + const int _duration_ms; + + /// Hyperion proto connection object + ProtoConnection _connection; +}; diff --git a/include/utils/Image.h b/include/utils/Image.h index 5e8b45ad..19b5ae35 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -11,207 +11,202 @@ class Image { public: - typedef Pixel_T pixel_type; + typedef Pixel_T pixel_type; - /// - /// Default constructor for an image - /// - Image() : - _width(1), - _height(1), - _pixels(new Pixel_T[2]), - _endOfPixels(_pixels + 1) - { - memset(_pixels, 0, 2*sizeof(Pixel_T)); - } + /// + /// Default constructor for an image + /// + Image() : + _width(1), + _height(1), + _pixels(new Pixel_T[2]), + _endOfPixels(_pixels + 1) + { + memset(_pixels, 0, 2*sizeof(Pixel_T)); + } - /// - /// Constructor for an image with specified width and height - /// - /// @param width The width of the image - /// @param height The height of the image - /// - Image(const unsigned width, const unsigned height) : - _width(width), - _height(height), - _pixels(new Pixel_T[width * height + 1]), - _endOfPixels(_pixels + width * height) - { - memset(_pixels, 0, (_width*_height+1)*sizeof(Pixel_T)); - } + /// + /// Constructor for an image with specified width and height + /// + /// @param width The width of the image + /// @param height The height of the image + /// + Image(const unsigned width, const unsigned height) : + _width(width), + _height(height), + _pixels(new Pixel_T[width * height + 1]), + _endOfPixels(_pixels + width * height) + { + memset(_pixels, 0, (_width*_height+1)*sizeof(Pixel_T)); + } - /// - /// Constructor for an image with specified width and height - /// - /// @param width The width of the image - /// @param height The height of the image - /// @param background The color of the image - /// - Image(const unsigned width, const unsigned height, const Pixel_T background) : - _width(width), - _height(height), - _pixels(new Pixel_T[width * height + 1]), - _endOfPixels(_pixels + width * height) - { - std::fill(_pixels, _endOfPixels, background); - } + /// + /// Constructor for an image with specified width and height + /// + /// @param width The width of the image + /// @param height The height of the image + /// @param background The color of the image + /// + Image(const unsigned width, const unsigned height, const Pixel_T background) : + _width(width), + _height(height), + _pixels(new Pixel_T[width * height + 1]), + _endOfPixels(_pixels + width * height) + { + std::fill(_pixels, _endOfPixels, background); + } - /// - /// Copy constructor for an image - /// - Image(const Image & other) : - _width(other._width), - _height(other._height), - _pixels(new Pixel_T[other._width * other._height + 1]), - _endOfPixels(_pixels + other._width * other._height) - { - memcpy(_pixels, other._pixels, other._width * other._height * sizeof(Pixel_T)); - } + /// + /// Copy constructor for an image + /// + Image(const Image & other) : + _width(other._width), + _height(other._height), + _pixels(new Pixel_T[other._width * other._height + 1]), + _endOfPixels(_pixels + other._width * other._height) + { + memcpy(_pixels, other._pixels, other._width * other._height * sizeof(Pixel_T)); + } - /// - /// Destructor - /// - ~Image() - { - delete[] _pixels; - } + /// + /// Destructor + /// + ~Image() + { + delete[] _pixels; + } - /// - /// Returns the width of the image - /// - /// @return The width of the image - /// - inline unsigned width() const - { - return _width; - } + /// + /// Returns the width of the image + /// + /// @return The width of the image + /// + inline unsigned width() const + { + return _width; + } - /// - /// Returns the height of the image - /// - /// @return The height of the image - /// - inline unsigned height() const - { - return _height; - } + /// + /// Returns the height of the image + /// + /// @return The height of the image + /// + inline unsigned height() const + { + return _height; + } - uint8_t alpha(const unsigned pixel) const - { - return (_pixels + pixel)->red; - } + uint8_t red(const unsigned pixel) const + { + return (_pixels + pixel)->red; + } - uint8_t red(const unsigned pixel) const - { - return (_pixels + pixel)->red; - } + uint8_t green(const unsigned pixel) const + { + return (_pixels + pixel)->green; + } - uint8_t green(const unsigned pixel) const - { - return (_pixels + pixel)->green; - } + uint8_t blue(const unsigned pixel) const + { + return (_pixels + pixel)->blue; + } - uint8_t blue(const unsigned pixel) const - { - return (_pixels + pixel)->blue; - } + /// + /// Returns a const reference to a specified pixel in the image + /// + /// @param x The x index + /// @param y The y index + /// + /// @return const reference to specified pixel + /// + const Pixel_T& operator()(const unsigned x, const unsigned y) const + { + return _pixels[toIndex(x,y)]; + } - /// - /// Returns a const reference to a specified pixel in the image - /// - /// @param x The x index - /// @param y The y index - /// - /// @return const reference to specified pixel - /// - const Pixel_T& operator()(const unsigned x, const unsigned y) const - { - return _pixels[toIndex(x,y)]; - } + /// + /// Returns a reference to a specified pixel in the image + /// + /// @param x The x index + /// @param y The y index + /// + /// @return reference to specified pixel + /// + Pixel_T& operator()(const unsigned x, const unsigned y) + { + return _pixels[toIndex(x,y)]; + } - /// - /// Returns a reference to a specified pixel in the image - /// - /// @param x The x index - /// @param y The y index - /// - /// @return reference to specified pixel - /// - Pixel_T& operator()(const unsigned x, const unsigned y) - { - return _pixels[toIndex(x,y)]; - } + /// Resize the image + /// @param width The width of the image + /// @param height The height of the image + void resize(const unsigned width, const unsigned height) + { + if ((width*height) > (_endOfPixels-_pixels)) + { + delete[] _pixels; + _pixels = new Pixel_T[width*height + 1]; + _endOfPixels = _pixels + width*height; + } - /// Resize the image - /// @param width The width of the image - /// @param height The height of the image - void resize(const unsigned width, const unsigned height) - { - if ((width*height) > (_endOfPixels-_pixels)) - { - delete[] _pixels; - _pixels = new Pixel_T[width*height + 1]; - _endOfPixels = _pixels + width*height; - } + _width = width; + _height = height; + } - _width = width; - _height = height; - } + /// + /// Copies another image into this image. The images should have exactly the same size. + /// + /// @param other The image to copy into this + /// + void copy(const Image& other) + { + assert(other._width == _width); + assert(other._height == _height); - /// - /// Copies another image into this image. The images should have exactly the same size. - /// - /// @param other The image to copy into this - /// - void copy(const Image& other) - { - assert(other._width == _width); - assert(other._height == _height); + memcpy(_pixels, other._pixels, _width*_height*sizeof(Pixel_T)); + } - memcpy(_pixels, other._pixels, _width*_height*sizeof(Pixel_T)); - } + /// + /// Returns a memory pointer to the first pixel in the image + /// @return The memory pointer to the first pixel + /// + Pixel_T* memptr() + { + return _pixels; + } - /// - /// Returns a memory pointer to the first pixel in the image - /// @return The memory pointer to the first pixel - /// - Pixel_T* memptr() - { - return _pixels; - } - - /// - /// Returns a const memory pointer to the first pixel in the image - /// @return The const memory pointer to the first pixel - /// - const Pixel_T* memptr() const - { - return _pixels; - } + /// + /// Returns a const memory pointer to the first pixel in the image + /// @return The const memory pointer to the first pixel + /// + const Pixel_T* memptr() const + { + return _pixels; + } private: - /// - /// Translate x and y coordinate to index of the underlying vector - /// - /// @param x The x index - /// @param y The y index - /// - /// @return The index into the underlying data-vector - /// - inline unsigned toIndex(const unsigned x, const unsigned y) const - { - return y*_width + x; - } + /// + /// Translate x and y coordinate to index of the underlying vector + /// + /// @param x The x index + /// @param y The y index + /// + /// @return The index into the underlying data-vector + /// + inline unsigned toIndex(const unsigned x, const unsigned y) const + { + return y*_width + x; + } private: - /// The width of the image - unsigned _width; - /// The height of the image - unsigned _height; + /// The width of the image + unsigned _width; + /// The height of the image + unsigned _height; - /// The pixels of the image - Pixel_T* _pixels; + /// The pixels of the image + Pixel_T* _pixels; - /// Pointer to the last(extra) pixel - Pixel_T* _endOfPixels; + /// Pointer to the last(extra) pixel + Pixel_T* _endOfPixels; }; diff --git a/include/utils/ImageResampler.h b/include/utils/ImageResampler.h new file mode 100644 index 00000000..b81eb5b7 --- /dev/null +++ b/include/utils/ImageResampler.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +class ImageResampler +{ +public: + ImageResampler(); + ~ImageResampler(); + + void setHorizontalPixelDecimation(int decimator); + + void setVerticalPixelDecimation(int decimator); + + void setCropping(int cropLeft, + int cropRight, + int cropTop, + int cropBottom); + + void set3D(VideoMode mode); + + void processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, + Image & outputImage) const; + +private: + static inline uint8_t clamp(int x); + static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b); + +private: + int _horizontalDecimation; + int _verticalDecimation; + int _cropLeft; + int _cropRight; + int _cropTop; + int _cropBottom; + VideoMode _videoMode; +}; diff --git a/include/grabber/PixelFormat.h b/include/utils/PixelFormat.h similarity index 70% rename from include/grabber/PixelFormat.h rename to include/utils/PixelFormat.h index 6afc7510..b269dfcb 100644 --- a/include/grabber/PixelFormat.h +++ b/include/utils/PixelFormat.h @@ -9,8 +9,9 @@ enum PixelFormat { PIXELFORMAT_YUYV, PIXELFORMAT_UYVY, - PIXELFORMAT_RGB32, - PIXELFORMAT_NO_CHANGE + PIXELFORMAT_RGB32, + PIXELFORMAT_BGR32, + PIXELFORMAT_NO_CHANGE }; inline PixelFormat parsePixelFormat(std::string pixelFormat) @@ -26,10 +27,14 @@ inline PixelFormat parsePixelFormat(std::string pixelFormat) { return PIXELFORMAT_UYVY; } - else if (pixelFormat == "rgb32") - { - return PIXELFORMAT_RGB32; - } + else if (pixelFormat == "rgb32") + { + return PIXELFORMAT_RGB32; + } + else if (pixelFormat == "bgr32") + { + return PIXELFORMAT_BGR32; + } // return the default NO_CHANGE return PIXELFORMAT_NO_CHANGE; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 62911dd7..680e7a53 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,15 +1,19 @@ - -# Define the current source locations -SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) -SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) - -add_subdirectory(hyperion) -add_subdirectory(blackborder) -add_subdirectory(jsonserver) -add_subdirectory(protoserver) -add_subdirectory(boblightserver) -add_subdirectory(leddevice) -add_subdirectory(utils) -add_subdirectory(xbmcvideochecker) -add_subdirectory(effectengine) -add_subdirectory(grabber) + +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc) + +add_subdirectory(hyperion) +add_subdirectory(blackborder) +add_subdirectory(jsonserver) + +if (ENABLE_PROTOBUF) + add_subdirectory(protoserver) +endif (ENABLE_PROTOBUF) + +add_subdirectory(boblightserver) +add_subdirectory(leddevice) +add_subdirectory(utils) +add_subdirectory(xbmcvideochecker) +add_subdirectory(effectengine) +add_subdirectory(grabber) diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index 322a5a98..85279243 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -1,4 +1,3 @@ - if (ENABLE_DISPMANX) add_subdirectory(dispmanx) endif (ENABLE_DISPMANX) @@ -6,3 +5,7 @@ endif (ENABLE_DISPMANX) if (ENABLE_V4L2) add_subdirectory(v4l2) endif (ENABLE_V4L2) + +if (ENABLE_X11) + add_subdirectory(x11) +endif() diff --git a/libsrc/grabber/v4l2/CMakeLists.txt b/libsrc/grabber/v4l2/CMakeLists.txt index 540a1217..5c7844e0 100644 --- a/libsrc/grabber/v4l2/CMakeLists.txt +++ b/libsrc/grabber/v4l2/CMakeLists.txt @@ -9,7 +9,6 @@ SET(V4L2_QT_HEADERS SET(V4L2_HEADERS ${CURRENT_HEADER_DIR}/VideoStandard.h - ${CURRENT_HEADER_DIR}/PixelFormat.h ) SET(V4L2_SOURCES diff --git a/libsrc/grabber/v4l2/V4L2Grabber.cpp b/libsrc/grabber/v4l2/V4L2Grabber.cpp index 2df80524..a3202f88 100644 --- a/libsrc/grabber/v4l2/V4L2Grabber.cpp +++ b/libsrc/grabber/v4l2/V4L2Grabber.cpp @@ -18,799 +18,721 @@ #define CLEAR(x) memset(&(x), 0, sizeof(x)) -static inline uint8_t clamp(int x) -{ - return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x)); -} - -static void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t & r, uint8_t & g, uint8_t & b) -{ - // see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion - int c = y - 16; - int d = u - 128; - int e = v - 128; - - r = clamp((298 * c + 409 * e + 128) >> 8); - g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8); - b = clamp((298 * c + 516 * d + 128) >> 8); -} - - V4L2Grabber::V4L2Grabber(const std::string & device, - int input, - VideoStandard videoStandard, - PixelFormat pixelFormat, - int width, - int height, - int frameDecimation, - int horizontalPixelDecimation, - int verticalPixelDecimation) : - _deviceName(device), - _ioMethod(IO_METHOD_MMAP), - _fileDescriptor(-1), - _buffers(), - _pixelFormat(pixelFormat), - _width(width), - _height(height), - _frameByteSize(-1), - _cropLeft(0), - _cropRight(0), - _cropTop(0), - _cropBottom(0), - _frameDecimation(std::max(1, frameDecimation)), - _horizontalPixelDecimation(std::max(1, horizontalPixelDecimation)), - _verticalPixelDecimation(std::max(1, verticalPixelDecimation)), - _noSignalCounterThreshold(50), - _noSignalThresholdColor(ColorRgb{0,0,0}), - _mode3D(VIDEO_2D), - _currentFrame(0), - _noSignalCounter(0), - _streamNotifier(nullptr) + int input, + VideoStandard videoStandard, + PixelFormat pixelFormat, + int width, + int height, + int frameDecimation, + int horizontalPixelDecimation, + int verticalPixelDecimation) : + _deviceName(device), + _ioMethod(IO_METHOD_MMAP), + _fileDescriptor(-1), + _buffers(), + _pixelFormat(pixelFormat), + _width(width), + _height(height), + _lineLength(-1), + _frameByteSize(-1), + _frameDecimation(std::max(1, frameDecimation)), + _noSignalCounterThreshold(50), + _noSignalThresholdColor(ColorRgb{0,0,0}), + _currentFrame(0), + _noSignalCounter(0), + _streamNotifier(nullptr), + _imageResampler() { - open_device(); - init_device(videoStandard, input); + _imageResampler.setHorizontalPixelDecimation(std::max(1, horizontalPixelDecimation)); + _imageResampler.setVerticalPixelDecimation(std::max(1, verticalPixelDecimation)); + + open_device(); + init_device(videoStandard, input); } V4L2Grabber::~V4L2Grabber() { - // stop if the grabber was not stopped - stop(); - uninit_device(); - close_device(); + // stop if the grabber was not stopped + stop(); + uninit_device(); + close_device(); } void V4L2Grabber::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) { - _cropLeft = cropLeft; - _cropRight = cropRight; - _cropTop = cropTop; - _cropBottom = cropBottom; + _imageResampler.setCropping(cropLeft, cropRight, cropTop, cropBottom); } void V4L2Grabber::set3D(VideoMode mode) { - _mode3D = mode; + _imageResampler.set3D(mode); } void V4L2Grabber::setSignalThreshold(double redSignalThreshold, double greenSignalThreshold, double blueSignalThreshold, int noSignalCounterThreshold) { - _noSignalThresholdColor.red = uint8_t(255*redSignalThreshold); - _noSignalThresholdColor.green = uint8_t(255*greenSignalThreshold); - _noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold); - _noSignalCounterThreshold = std::max(1, noSignalCounterThreshold); + _noSignalThresholdColor.red = uint8_t(255*redSignalThreshold); + _noSignalThresholdColor.green = uint8_t(255*greenSignalThreshold); + _noSignalThresholdColor.blue = uint8_t(255*blueSignalThreshold); + _noSignalCounterThreshold = std::max(1, noSignalCounterThreshold); - std::cout << "V4L2 grabber signal threshold set to: " << _noSignalThresholdColor << std::endl; + std::cout << "V4L2 grabber signal threshold set to: " << _noSignalThresholdColor << std::endl; } void V4L2Grabber::start() { - if (_streamNotifier != nullptr && !_streamNotifier->isEnabled()) - { - _streamNotifier->setEnabled(true); - start_capturing(); - std::cout << "V4L2 grabber started" << std::endl; - } + if (_streamNotifier != nullptr && !_streamNotifier->isEnabled()) + { + _streamNotifier->setEnabled(true); + start_capturing(); + std::cout << "V4L2 grabber started" << std::endl; + } } void V4L2Grabber::stop() { - if (_streamNotifier != nullptr && _streamNotifier->isEnabled()) - { - stop_capturing(); - _streamNotifier->setEnabled(false); - std::cout << "V4L2 grabber stopped" << std::endl; - } + if (_streamNotifier != nullptr && _streamNotifier->isEnabled()) + { + stop_capturing(); + _streamNotifier->setEnabled(false); + std::cout << "V4L2 grabber stopped" << std::endl; + } } void V4L2Grabber::open_device() { - struct stat st; + struct stat st; - if (-1 == stat(_deviceName.c_str(), &st)) - { - std::ostringstream oss; - oss << "Cannot identify '" << _deviceName << "'"; - throw_errno_exception(oss.str()); - } + if (-1 == stat(_deviceName.c_str(), &st)) + { + std::ostringstream oss; + oss << "Cannot identify '" << _deviceName << "'"; + throw_errno_exception(oss.str()); + } - if (!S_ISCHR(st.st_mode)) - { - std::ostringstream oss; - oss << "'" << _deviceName << "' is no device"; - throw_exception(oss.str()); - } + if (!S_ISCHR(st.st_mode)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no device"; + throw_exception(oss.str()); + } - _fileDescriptor = open(_deviceName.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); + _fileDescriptor = open(_deviceName.c_str(), O_RDWR /* required */ | O_NONBLOCK, 0); - if (-1 == _fileDescriptor) - { - std::ostringstream oss; - oss << "Cannot open '" << _deviceName << "'"; - throw_errno_exception(oss.str()); - } + if (-1 == _fileDescriptor) + { + std::ostringstream oss; + oss << "Cannot open '" << _deviceName << "'"; + throw_errno_exception(oss.str()); + } - // create the notifier for when a new frame is available - _streamNotifier = new QSocketNotifier(_fileDescriptor, QSocketNotifier::Read); - _streamNotifier->setEnabled(false); - connect(_streamNotifier, SIGNAL(activated(int)), this, SLOT(read_frame())); + // create the notifier for when a new frame is available + _streamNotifier = new QSocketNotifier(_fileDescriptor, QSocketNotifier::Read); + _streamNotifier->setEnabled(false); + connect(_streamNotifier, SIGNAL(activated(int)), this, SLOT(read_frame())); } void V4L2Grabber::close_device() { - if (-1 == close(_fileDescriptor)) - throw_errno_exception("close"); + if (-1 == close(_fileDescriptor)) + throw_errno_exception("close"); - _fileDescriptor = -1; + _fileDescriptor = -1; - if (_streamNotifier != nullptr) - { - delete _streamNotifier; - _streamNotifier = nullptr; - } + if (_streamNotifier != nullptr) + { + delete _streamNotifier; + _streamNotifier = nullptr; + } } void V4L2Grabber::init_read(unsigned int buffer_size) { - _buffers.resize(1); + _buffers.resize(1); - _buffers[0].length = buffer_size; - _buffers[0].start = malloc(buffer_size); + _buffers[0].length = buffer_size; + _buffers[0].start = malloc(buffer_size); - if (!_buffers[0].start) { - throw_exception("Out of memory"); - } + if (!_buffers[0].start) { + throw_exception("Out of memory"); + } } void V4L2Grabber::init_mmap() { - struct v4l2_requestbuffers req; + struct v4l2_requestbuffers req; - CLEAR(req); + CLEAR(req); - req.count = 4; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_MMAP; + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { - if (EINVAL == errno) { - std::ostringstream oss; - oss << "'" << _deviceName << "' does not support memory mapping"; - throw_exception(oss.str()); - } else { - throw_errno_exception("VIDIOC_REQBUFS"); - } - } + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support memory mapping"; + throw_exception(oss.str()); + } else { + throw_errno_exception("VIDIOC_REQBUFS"); + } + } - if (req.count < 2) { - std::ostringstream oss; - oss << "Insufficient buffer memory on " << _deviceName; - throw_exception(oss.str()); - } + if (req.count < 2) { + std::ostringstream oss; + oss << "Insufficient buffer memory on " << _deviceName; + throw_exception(oss.str()); + } - _buffers.resize(req.count); + _buffers.resize(req.count); - for (size_t n_buffers = 0; n_buffers < req.count; ++n_buffers) { - struct v4l2_buffer buf; + for (size_t n_buffers = 0; n_buffers < req.count; ++n_buffers) { + struct v4l2_buffer buf; - CLEAR(buf); + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = n_buffers; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; - if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) - throw_errno_exception("VIDIOC_QUERYBUF"); + if (-1 == xioctl(VIDIOC_QUERYBUF, &buf)) + throw_errno_exception("VIDIOC_QUERYBUF"); - _buffers[n_buffers].length = buf.length; - _buffers[n_buffers].start = - mmap(NULL /* start anywhere */, - buf.length, - PROT_READ | PROT_WRITE /* required */, - MAP_SHARED /* recommended */, - _fileDescriptor, buf.m.offset); + _buffers[n_buffers].length = buf.length; + _buffers[n_buffers].start = + mmap(NULL /* start anywhere */, + buf.length, + PROT_READ | PROT_WRITE /* required */, + MAP_SHARED /* recommended */, + _fileDescriptor, buf.m.offset); - if (MAP_FAILED == _buffers[n_buffers].start) - throw_errno_exception("mmap"); - } + if (MAP_FAILED == _buffers[n_buffers].start) + throw_errno_exception("mmap"); + } } void V4L2Grabber::init_userp(unsigned int buffer_size) { - struct v4l2_requestbuffers req; + struct v4l2_requestbuffers req; - CLEAR(req); + CLEAR(req); - req.count = 4; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - req.memory = V4L2_MEMORY_USERPTR; + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { - if (EINVAL == errno) - { - std::ostringstream oss; - oss << "'" << _deviceName << "' does not support user pointer"; - throw_exception(oss.str()); - } else { - throw_errno_exception("VIDIOC_REQBUFS"); - } - } + if (-1 == xioctl(VIDIOC_REQBUFS, &req)) { + if (EINVAL == errno) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support user pointer"; + throw_exception(oss.str()); + } else { + throw_errno_exception("VIDIOC_REQBUFS"); + } + } - _buffers.resize(4); + _buffers.resize(4); - for (size_t n_buffers = 0; n_buffers < 4; ++n_buffers) { - _buffers[n_buffers].length = buffer_size; - _buffers[n_buffers].start = malloc(buffer_size); + for (size_t n_buffers = 0; n_buffers < 4; ++n_buffers) { + _buffers[n_buffers].length = buffer_size; + _buffers[n_buffers].start = malloc(buffer_size); - if (!_buffers[n_buffers].start) { - throw_exception("Out of memory"); - } - } + if (!_buffers[n_buffers].start) { + throw_exception("Out of memory"); + } + } } void V4L2Grabber::init_device(VideoStandard videoStandard, int input) { - struct v4l2_capability cap; - if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) - { - if (EINVAL == errno) { - std::ostringstream oss; - oss << "'" << _deviceName << "' is no V4L2 device"; - throw_exception(oss.str()); - } else { - throw_errno_exception("VIDIOC_QUERYCAP"); - } - } + struct v4l2_capability cap; + if (-1 == xioctl(VIDIOC_QUERYCAP, &cap)) + { + if (EINVAL == errno) { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no V4L2 device"; + throw_exception(oss.str()); + } else { + throw_errno_exception("VIDIOC_QUERYCAP"); + } + } - if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) - { - std::ostringstream oss; - oss << "'" << _deviceName << "' is no video capture device"; - throw_exception(oss.str()); - } + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' is no video capture device"; + throw_exception(oss.str()); + } - switch (_ioMethod) { - case IO_METHOD_READ: - if (!(cap.capabilities & V4L2_CAP_READWRITE)) - { - std::ostringstream oss; - oss << "'" << _deviceName << "' does not support read i/o"; - throw_exception(oss.str()); - } - break; + switch (_ioMethod) { + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support read i/o"; + throw_exception(oss.str()); + } + break; - case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - if (!(cap.capabilities & V4L2_CAP_STREAMING)) - { - std::ostringstream oss; - oss << "'" << _deviceName << "' does not support streaming i/o"; - throw_exception(oss.str()); - } - break; - } + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) + { + std::ostringstream oss; + oss << "'" << _deviceName << "' does not support streaming i/o"; + throw_exception(oss.str()); + } + break; + } - /* Select video input, video standard and tune here. */ + /* Select video input, video standard and tune here. */ - struct v4l2_cropcap cropcap; - CLEAR(cropcap); + struct v4l2_cropcap cropcap; + CLEAR(cropcap); - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { - struct v4l2_crop crop; - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - crop.c = cropcap.defrect; /* reset to default */ + if (0 == xioctl(VIDIOC_CROPCAP, &cropcap)) { + struct v4l2_crop crop; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c = cropcap.defrect; /* reset to default */ - if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { - switch (errno) { - case EINVAL: - /* Cropping not supported. */ - break; - default: - /* Errors ignored. */ - break; - } - } - } else { - /* Errors ignored. */ - } + if (-1 == xioctl(VIDIOC_S_CROP, &crop)) { + switch (errno) { + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; + } + } + } else { + /* Errors ignored. */ + } - // set input if needed - if (input >= 0) - { - if (-1 == xioctl(VIDIOC_S_INPUT, &input)) - { - throw_errno_exception("VIDIOC_S_INPUT"); - } - } + // set input if needed + if (input >= 0) + { + if (-1 == xioctl(VIDIOC_S_INPUT, &input)) + { + throw_errno_exception("VIDIOC_S_INPUT"); + } + } - // set the video standard if needed - switch (videoStandard) - { - case VIDEOSTANDARD_PAL: - { - v4l2_std_id std_id = V4L2_STD_PAL; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - throw_errno_exception("VIDIOC_S_STD"); - } - } - break; - case VIDEOSTANDARD_NTSC: - { - v4l2_std_id std_id = V4L2_STD_NTSC; - if (-1 == xioctl(VIDIOC_S_STD, &std_id)) - { - throw_errno_exception("VIDIOC_S_STD"); - } - } - break; - case VIDEOSTANDARD_NO_CHANGE: - default: - // No change to device settings - break; - } + // set the video standard if needed + switch (videoStandard) + { + case VIDEOSTANDARD_PAL: + { + v4l2_std_id std_id = V4L2_STD_PAL; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + throw_errno_exception("VIDIOC_S_STD"); + } + } + break; + case VIDEOSTANDARD_NTSC: + { + v4l2_std_id std_id = V4L2_STD_NTSC; + if (-1 == xioctl(VIDIOC_S_STD, &std_id)) + { + throw_errno_exception("VIDIOC_S_STD"); + } + } + break; + case VIDEOSTANDARD_NO_CHANGE: + default: + // No change to device settings + break; + } - // get the current settings - struct v4l2_format fmt; - CLEAR(fmt); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) - { - throw_errno_exception("VIDIOC_G_FMT"); - } + // get the current settings + struct v4l2_format fmt; + CLEAR(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_G_FMT"); + } - // set the requested pixel format - switch (_pixelFormat) - { - case PIXELFORMAT_UYVY: - fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; - break; - case PIXELFORMAT_YUYV: - fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; - break; - case PIXELFORMAT_RGB32: - fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; - break; - case PIXELFORMAT_NO_CHANGE: - default: - // No change to device settings - break; - } + // set the requested pixel format + switch (_pixelFormat) + { + case PIXELFORMAT_UYVY: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + break; + case PIXELFORMAT_YUYV: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + break; + case PIXELFORMAT_RGB32: + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + break; + case PIXELFORMAT_NO_CHANGE: + default: + // No change to device settings + break; + } - // set the requested withd and height - if (_width > 0 || _height > 0) - { - if (_width > 0) - { - fmt.fmt.pix.width = _width; - } + // set the requested withd and height + if (_width > 0 || _height > 0) + { + if (_width > 0) + { + fmt.fmt.pix.width = _width; + } - if (fmt.fmt.pix.height > 0) - { - fmt.fmt.pix.height = _height; - } - } + if (fmt.fmt.pix.height > 0) + { + fmt.fmt.pix.height = _height; + } + } - // set the settings - if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) - { - throw_errno_exception("VIDIOC_S_FMT"); - } + // set the line length + _lineLength = fmt.fmt.pix.bytesperline; - // get the format settings again - // (the size may not have been accepted without an error) - if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) - { - throw_errno_exception("VIDIOC_G_FMT"); - } + // set the settings + if (-1 == xioctl(VIDIOC_S_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_S_FMT"); + } - // store width & height - _width = fmt.fmt.pix.width; - _height = fmt.fmt.pix.height; + // get the format settings again + // (the size may not have been accepted without an error) + if (-1 == xioctl(VIDIOC_G_FMT, &fmt)) + { + throw_errno_exception("VIDIOC_G_FMT"); + } - // print the eventually used width and height - std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; + // store width & height + _width = fmt.fmt.pix.width; + _height = fmt.fmt.pix.height; - // check pixel format and frame size - switch (fmt.fmt.pix.pixelformat) - { - case V4L2_PIX_FMT_UYVY: - _pixelFormat = PIXELFORMAT_UYVY; - _frameByteSize = _width * _height * 2; - std::cout << "V4L2 pixel format=UYVY" << std::endl; - break; - case V4L2_PIX_FMT_YUYV: - _pixelFormat = PIXELFORMAT_YUYV; - _frameByteSize = _width * _height * 2; - std::cout << "V4L2 pixel format=YUYV" << std::endl; - break; - case V4L2_PIX_FMT_RGB32: - _pixelFormat = PIXELFORMAT_RGB32; - _frameByteSize = _width * _height * 4; - std::cout << "V4L2 pixel format=RGB32" << std::endl; - break; - default: - throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); - } + // print the eventually used width and height + std::cout << "V4L2 width=" << _width << " height=" << _height << std::endl; - switch (_ioMethod) { - case IO_METHOD_READ: - init_read(fmt.fmt.pix.sizeimage); - break; + // check pixel format and frame size + switch (fmt.fmt.pix.pixelformat) + { + case V4L2_PIX_FMT_UYVY: + _pixelFormat = PIXELFORMAT_UYVY; + _frameByteSize = _width * _height * 2; + std::cout << "V4L2 pixel format=UYVY" << std::endl; + break; + case V4L2_PIX_FMT_YUYV: + _pixelFormat = PIXELFORMAT_YUYV; + _frameByteSize = _width * _height * 2; + std::cout << "V4L2 pixel format=YUYV" << std::endl; + break; + case V4L2_PIX_FMT_RGB32: + _pixelFormat = PIXELFORMAT_RGB32; + _frameByteSize = _width * _height * 4; + std::cout << "V4L2 pixel format=RGB32" << std::endl; + break; + default: + throw_exception("Only pixel formats UYVY, YUYV, and RGB32 are supported"); + } - case IO_METHOD_MMAP: - init_mmap(); - break; + switch (_ioMethod) { + case IO_METHOD_READ: + init_read(fmt.fmt.pix.sizeimage); + break; - case IO_METHOD_USERPTR: - init_userp(fmt.fmt.pix.sizeimage); - break; - } + case IO_METHOD_MMAP: + init_mmap(); + break; + + case IO_METHOD_USERPTR: + init_userp(fmt.fmt.pix.sizeimage); + break; + } } void V4L2Grabber::uninit_device() { - switch (_ioMethod) { - case IO_METHOD_READ: - free(_buffers[0].start); - break; + switch (_ioMethod) { + case IO_METHOD_READ: + free(_buffers[0].start); + break; - case IO_METHOD_MMAP: - for (size_t i = 0; i < _buffers.size(); ++i) - if (-1 == munmap(_buffers[i].start, _buffers[i].length)) - throw_errno_exception("munmap"); - break; + case IO_METHOD_MMAP: + for (size_t i = 0; i < _buffers.size(); ++i) + if (-1 == munmap(_buffers[i].start, _buffers[i].length)) + throw_errno_exception("munmap"); + break; - case IO_METHOD_USERPTR: - for (size_t i = 0; i < _buffers.size(); ++i) - free(_buffers[i].start); - break; - } + case IO_METHOD_USERPTR: + for (size_t i = 0; i < _buffers.size(); ++i) + free(_buffers[i].start); + break; + } - _buffers.resize(0); + _buffers.resize(0); } void V4L2Grabber::start_capturing() { - switch (_ioMethod) { - case IO_METHOD_READ: - /* Nothing to do. */ - break; + switch (_ioMethod) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; - case IO_METHOD_MMAP: - { - for (size_t i = 0; i < _buffers.size(); ++i) { - struct v4l2_buffer buf; + case IO_METHOD_MMAP: + { + for (size_t i = 0; i < _buffers.size(); ++i) { + struct v4l2_buffer buf; - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = i; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - throw_errno_exception("VIDIOC_QBUF"); - } - v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_STREAMON, &type)) - throw_errno_exception("VIDIOC_STREAMON"); - break; - } - case IO_METHOD_USERPTR: - { - for (size_t i = 0; i < _buffers.size(); ++i) { - struct v4l2_buffer buf; + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + throw_errno_exception("VIDIOC_QBUF"); + } + v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMON, &type)) + throw_errno_exception("VIDIOC_STREAMON"); + break; + } + case IO_METHOD_USERPTR: + { + for (size_t i = 0; i < _buffers.size(); ++i) { + struct v4l2_buffer buf; - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; - buf.index = i; - buf.m.userptr = (unsigned long)_buffers[i].start; - buf.length = _buffers[i].length; + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + buf.index = i; + buf.m.userptr = (unsigned long)_buffers[i].start; + buf.length = _buffers[i].length; - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - throw_errno_exception("VIDIOC_QBUF"); - } - v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_STREAMON, &type)) - throw_errno_exception("VIDIOC_STREAMON"); - break; - } - } + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + throw_errno_exception("VIDIOC_QBUF"); + } + v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMON, &type)) + throw_errno_exception("VIDIOC_STREAMON"); + break; + } + } } void V4L2Grabber::stop_capturing() { - enum v4l2_buf_type type; + enum v4l2_buf_type type; - switch (_ioMethod) { - case IO_METHOD_READ: - /* Nothing to do. */ - break; + switch (_ioMethod) { + case IO_METHOD_READ: + /* Nothing to do. */ + break; - case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(VIDIOC_STREAMOFF, &type)) - throw_errno_exception("VIDIOC_STREAMOFF"); - break; - } + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (-1 == xioctl(VIDIOC_STREAMOFF, &type)) + throw_errno_exception("VIDIOC_STREAMOFF"); + break; + } } int V4L2Grabber::read_frame() { - bool rc = false; + bool rc = false; - struct v4l2_buffer buf; + struct v4l2_buffer buf; - switch (_ioMethod) { - case IO_METHOD_READ: - int size; - if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) - { - switch (errno) - { - case EAGAIN: - return 0; + switch (_ioMethod) { + case IO_METHOD_READ: + int size; + if ((size = read(_fileDescriptor, _buffers[0].start, _buffers[0].length)) == -1) + { + switch (errno) + { + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - throw_errno_exception("read"); - } - } + default: + throw_errno_exception("read"); + } + } - rc = process_image(_buffers[0].start, size); - break; + rc = process_image(_buffers[0].start, size); + break; - case IO_METHOD_MMAP: - CLEAR(buf); + case IO_METHOD_MMAP: + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) - { - switch (errno) - { - case EAGAIN: - return 0; + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - throw_errno_exception("VIDIOC_DQBUF"); - } - } + default: + throw_errno_exception("VIDIOC_DQBUF"); + } + } - assert(buf.index < _buffers.size()); + assert(buf.index < _buffers.size()); - rc = process_image(_buffers[buf.index].start, buf.bytesused); + rc = process_image(_buffers[buf.index].start, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { - throw_errno_exception("VIDIOC_QBUF"); - } + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { + throw_errno_exception("VIDIOC_QBUF"); + } - break; + break; - case IO_METHOD_USERPTR: - CLEAR(buf); + case IO_METHOD_USERPTR: + CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; - if (-1 == xioctl(VIDIOC_DQBUF, &buf)) - { - switch (errno) - { - case EAGAIN: - return 0; + if (-1 == xioctl(VIDIOC_DQBUF, &buf)) + { + switch (errno) + { + case EAGAIN: + return 0; - case EIO: - /* Could ignore EIO, see spec. */ + case EIO: + /* Could ignore EIO, see spec. */ - /* fall through */ + /* fall through */ - default: - throw_errno_exception("VIDIOC_DQBUF"); - } - } + default: + throw_errno_exception("VIDIOC_DQBUF"); + } + } - for (size_t i = 0; i < _buffers.size(); ++i) - { - if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) - { - break; - } - } + for (size_t i = 0; i < _buffers.size(); ++i) + { + if (buf.m.userptr == (unsigned long)_buffers[i].start && buf.length == _buffers[i].length) + { + break; + } + } - rc = process_image((void *)buf.m.userptr, buf.bytesused); + rc = process_image((void *)buf.m.userptr, buf.bytesused); - if (-1 == xioctl(VIDIOC_QBUF, &buf)) - { - throw_errno_exception("VIDIOC_QBUF"); - } - break; - } + if (-1 == xioctl(VIDIOC_QBUF, &buf)) + { + throw_errno_exception("VIDIOC_QBUF"); + } + break; + } - return rc ? 1 : 0; + return rc ? 1 : 0; } bool V4L2Grabber::process_image(const void *p, int size) { - if (++_currentFrame >= _frameDecimation) - { - // We do want a new frame... + if (++_currentFrame >= _frameDecimation) + { + // We do want a new frame... - if (size != _frameByteSize) - { - std::cout << "Frame too small: " << size << " != " << _frameByteSize << std::endl; - } - else - { - process_image(reinterpret_cast(p)); - _currentFrame = 0; // restart counting - return true; - } - } + if (size != _frameByteSize) + { + std::cout << "Frame too small: " << size << " != " << _frameByteSize << std::endl; + } + else + { + process_image(reinterpret_cast(p)); + _currentFrame = 0; // restart counting + return true; + } + } - return false; + return false; } void V4L2Grabber::process_image(const uint8_t * data) { - int width = _width; - int height = _height; + Image image(0, 0); + _imageResampler.processImage(data, _width, _height, _lineLength, _pixelFormat, image); - switch (_mode3D) - { - case VIDEO_3DSBS: - width = _width/2; - break; - case VIDEO_3DTAB: - height = _height/2; - break; - default: - break; - } + // check signal (only in center of the resulting image, because some grabbers have noise values along the borders) + bool noSignal = true; + for (unsigned x = 0; noSignal && x < (image.width()>>1); ++x) + { + int xImage = (image.width()>>2) + x; - // create output structure - int outputWidth = (width - _cropLeft - _cropRight + _horizontalPixelDecimation/2) / _horizontalPixelDecimation; - int outputHeight = (height - _cropTop - _cropBottom + _verticalPixelDecimation/2) / _verticalPixelDecimation; - Image image(outputWidth, outputHeight); + for (unsigned y = 0; noSignal && y < (image.height()>>1); ++y) + { + int yImage = (image.height()>>2) + y; - for (int ySource = _cropTop + _verticalPixelDecimation/2, yDest = 0; ySource < height - _cropBottom; ySource += _verticalPixelDecimation, ++yDest) - { - for (int xSource = _cropLeft + _horizontalPixelDecimation/2, xDest = 0; xSource < width - _cropRight; xSource += _horizontalPixelDecimation, ++xDest) - { - ColorRgb & rgb = image(xDest, yDest); + ColorRgb & rgb = image(xImage, yImage); + noSignal &= rgb <= _noSignalThresholdColor; + } + } - switch (_pixelFormat) - { - case PIXELFORMAT_UYVY: - { - int index = (_width * ySource + xSource) * 2; - uint8_t y = data[index+1]; - uint8_t u = (xSource%2 == 0) ? data[index ] : data[index-2]; - uint8_t v = (xSource%2 == 0) ? data[index+2] : data[index ]; - yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); - } - break; - case PIXELFORMAT_YUYV: - { - int index = (_width * ySource + xSource) * 2; - uint8_t y = data[index]; - uint8_t u = (xSource%2 == 0) ? data[index+1] : data[index-1]; - uint8_t v = (xSource%2 == 0) ? data[index+3] : data[index+1]; - yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); - } - break; - case PIXELFORMAT_RGB32: - { - int index = (_width * ySource + xSource) * 4; - rgb.red = data[index ]; - rgb.green = data[index+1]; - rgb.blue = data[index+2]; - } - break; - default: - // this should not be possible - break; - } - } - } + if (noSignal) + { + ++_noSignalCounter; + } + else + { + if (_noSignalCounter >= _noSignalCounterThreshold) + { + std::cout << "V4L2 Grabber: " << "Signal detected" << std::endl; + } - // check signal (only in center of the resulting image, because some grabbers have noise values along the borders) - bool noSignal = true; - for (unsigned x = 0; noSignal && x < (image.width()>>1); ++x) - { - int xImage = (image.width()>>2) + x; + _noSignalCounter = 0; + } - for (unsigned y = 0; noSignal && y < (image.height()>>1); ++y) - { - int yImage = (image.height()>>2) + y; - - ColorRgb & rgb = image(xImage, yImage); - noSignal &= rgb <= _noSignalThresholdColor; - } - } - - if (noSignal) - { - ++_noSignalCounter; - } - else - { - if (_noSignalCounter >= _noSignalCounterThreshold) - { - std::cout << "V4L2 Grabber: " << "Signal detected" << std::endl; - } - - _noSignalCounter = 0; - } - - if (_noSignalCounter < _noSignalCounterThreshold) - { - emit newFrame(image); - } - else if (_noSignalCounter == _noSignalCounterThreshold) - { - std::cout << "V4L2 Grabber: " << "Signal lost" << std::endl; - } + if (_noSignalCounter < _noSignalCounterThreshold) + { + emit newFrame(image); + } + else if (_noSignalCounter == _noSignalCounterThreshold) + { + std::cout << "V4L2 Grabber: " << "Signal lost" << std::endl; + } } int V4L2Grabber::xioctl(int request, void *arg) { - int r; + int r; - do - { - r = ioctl(_fileDescriptor, request, arg); - } - while (-1 == r && EINTR == errno); + do + { + r = ioctl(_fileDescriptor, request, arg); + } + while (-1 == r && EINTR == errno); - return r; + return r; } void V4L2Grabber::throw_exception(const std::string & error) { - std::ostringstream oss; - oss << error << " error"; - throw std::runtime_error(oss.str()); + std::ostringstream oss; + oss << error << " error"; + throw std::runtime_error(oss.str()); } void V4L2Grabber::throw_errno_exception(const std::string & error) { - std::ostringstream oss; - oss << error << " error " << errno << ", " << strerror(errno); - throw std::runtime_error(oss.str()); + std::ostringstream oss; + oss << error << " error " << errno << ", " << strerror(errno); + throw std::runtime_error(oss.str()); } diff --git a/libsrc/grabber/x11/CMakeLists.txt b/libsrc/grabber/x11/CMakeLists.txt new file mode 100644 index 00000000..869fb4bf --- /dev/null +++ b/libsrc/grabber/x11/CMakeLists.txt @@ -0,0 +1,37 @@ +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/x11) + +# Find X11 +find_package(X11 REQUIRED) + +include_directories( + ${QT_INCLUDES} + ${X11_INCLUDES} +) + +SET(X11_QT_HEADERS + ${CURRENT_HEADER_DIR}/X11Grabber.h +) + +SET(X11_HEADERS + ${CURRENT_HEADER_DIR}/X11Grabber.h +) + +SET(X11_SOURCES + ${CURRENT_SOURCE_DIR}/X11Grabber.cpp +) + +QT4_WRAP_CPP(X11_HEADERS_MOC ${X11_QT_HEADERS}) + +add_library(x11-grabber + ${X11_HEADERS} + ${X11_SOURCES} + ${X11_QT_HEADERS} + ${X11_HEADERS_MOC} +) + +target_link_libraries(x11-grabber + hyperion + ${QT_LIBRARIES} +) diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp new file mode 100644 index 00000000..0787e056 --- /dev/null +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -0,0 +1,98 @@ +// STL includes +#include +#include + +// X11 includes +#include + +// X11Grabber includes +#include + +X11Grabber::X11Grabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) : + _imageResampler(), + _cropLeft(cropLeft), + _cropRight(cropRight), + _cropTop(cropTop), + _cropBottom(cropBottom), + _x11Display(nullptr), + _screenWidth(0), + _screenHeight(0), + _image(0,0) +{ + _imageResampler.setHorizontalPixelDecimation(horizontalPixelDecimation); + _imageResampler.setVerticalPixelDecimation(verticalPixelDecimation); + _imageResampler.setCropping(0, 0, 0, 0); // cropping is performed by XGetImage +} + +X11Grabber::~X11Grabber() +{ + if (_x11Display != nullptr) + { + XCloseDisplay(_x11Display); + } +} + +int X11Grabber::open() +{ + const char * display_name = nullptr; + _x11Display = XOpenDisplay(display_name); + + if (_x11Display == nullptr) + { + std::cerr << "Failed to open the default X11-display" << std::endl; + return -1; + } + + return 0; +} + +Image & X11Grabber::grab() +{ + if (_x11Display == nullptr) + { + open(); + } + + updateScreenDimensions(); + + const unsigned croppedWidth = _screenWidth - _cropLeft - _cropRight; + const unsigned croppedHeight = _screenHeight - _cropTop - _cropBottom; + + // Capture the current screen + XImage * xImage = XGetImage(_x11Display, DefaultRootWindow(_x11Display), _cropLeft, _cropTop, croppedWidth, croppedHeight, AllPlanes, ZPixmap); + if (xImage == nullptr) + { + std::cerr << "Grab failed" << std::endl; + return _image; + } + + _imageResampler.processImage(reinterpret_cast(xImage->data), xImage->width, xImage->height, xImage->bytes_per_line, PIXELFORMAT_BGR32, _image); + + // Cleanup allocated resources of the X11 grab + XDestroyImage(xImage); + + return _image; +} + +int X11Grabber::updateScreenDimensions() +{ + XWindowAttributes window_attributes_return; + const Status status = XGetWindowAttributes(_x11Display, DefaultRootWindow(_x11Display), &window_attributes_return); + if (status == 0) + { + std::cerr << "Failed to obtain window attributes" << std::endl; + return -1; + } + + if (_screenWidth == unsigned(window_attributes_return.width) && _screenHeight == unsigned(window_attributes_return.height)) + { + // No update required + return 0; + } + std::cout << "Update of screen resolution: [" << _screenWidth << "x" << _screenHeight <<"] => "; + _screenWidth = window_attributes_return.width; + _screenHeight = window_attributes_return.height; + std::cout << "[" << _screenWidth << "x" << _screenHeight <<"]" << std::endl; + + return 0; +} diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index a81e4f3c..5e76d8e8 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -243,8 +243,13 @@ LedDevice * Hyperion::createColorSmoothing(const Json::Value & smoothingConfig, } else { + const unsigned updateDelay = smoothingConfig.get("updateDelay", Json::Value(0u)).asUInt(); std::cout << "Creating linear smoothing" << std::endl; - return new LinearColorSmoothing(ledDevice, smoothingConfig["updateFrequency"].asDouble(), smoothingConfig["time_ms"].asInt()); + return new LinearColorSmoothing( + ledDevice, + smoothingConfig["updateFrequency"].asDouble(), + smoothingConfig["time_ms"].asInt(), + updateDelay); } } else diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index 0fe3e208..eef5569d 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -3,29 +3,38 @@ #include "LinearColorSmoothing.h" -LinearColorSmoothing::LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency_hz, int settlingTime_ms) : +LinearColorSmoothing::LinearColorSmoothing( + LedDevice * ledDevice, + double ledUpdateFrequency_hz, + int settlingTime_ms, + unsigned updateDelay) : QObject(), LedDevice(), _ledDevice(ledDevice), _updateInterval(1000 / ledUpdateFrequency_hz), _settlingTime(settlingTime_ms), - _timer() + _timer(), + _outputDelay(updateDelay) { _timer.setSingleShot(false); _timer.setInterval(_updateInterval); connect(&_timer, SIGNAL(timeout()), this, SLOT(updateLeds())); + + std::cout << "Created linear-smoothing(interval_ms=" << _updateInterval << ";settlingTime_ms=" << settlingTime_ms << ";updateDelay=" << _outputDelay << std::endl; } LinearColorSmoothing::~LinearColorSmoothing() { + // Make sure to switch off the underlying led-device (because switchOff is no longer forwarded) + _ledDevice->switchOff(); delete _ledDevice; } int LinearColorSmoothing::write(const std::vector &ledValues) { // received a new target color - if (_previousValues.size() == 0) + if (_previousValues.empty()) { // not initialized yet _targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime; @@ -46,17 +55,20 @@ int LinearColorSmoothing::write(const std::vector &ledValues) int LinearColorSmoothing::switchOff() { - // stop smoothing filter - _timer.stop(); + // We will keep updating the leds (but with pure-black) - // return to uninitialized state - _previousValues.clear(); - _previousTime = 0; - _targetValues.clear(); + // Clear the smoothing parameters + std::fill(_targetValues.begin(), _targetValues.end(), ColorRgb::BLACK); _targetTime = 0; - // finally switch off all leds - return _ledDevice->switchOff(); + // Erase the output-queue + for (unsigned i=0; i<_outputQueue.size(); ++i) + { + _outputQueue.push_back(_targetValues); + _outputQueue.pop_front(); + } + + return 0; } void LinearColorSmoothing::updateLeds() @@ -69,7 +81,7 @@ void LinearColorSmoothing::updateLeds() memcpy(_previousValues.data(), _targetValues.data(), _targetValues.size() * sizeof(ColorRgb)); _previousTime = now; - _ledDevice->write(_previousValues); + queueColors(_previousValues); } else { @@ -86,6 +98,26 @@ void LinearColorSmoothing::updateLeds() } _previousTime = now; - _ledDevice->write(_previousValues); + queueColors(_previousValues); + } +} + +void LinearColorSmoothing::queueColors(const std::vector & ledColors) +{ + if (_outputDelay == 0) + { + // No output delay => immediate write + _ledDevice->write(ledColors); + } + else + { + // Push the new colors in the delay-buffer + _outputQueue.push_back(ledColors); + // If the delay-buffer is filled pop the front and write to device + if (_outputQueue.size() > _outputDelay) + { + _ledDevice->write(_outputQueue.front()); + _outputQueue.pop_front(); + } } } diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/libsrc/hyperion/LinearColorSmoothing.h index 5c27862d..2b75059e 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/libsrc/hyperion/LinearColorSmoothing.h @@ -23,7 +23,8 @@ public: /// @param LedDevice the led device /// @param LedUpdatFrequency The frequency at which the leds will be updated (Hz) /// @param settingTime The time after which the updated led values have been fully applied (sec) - LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime); + /// @param updateDelay The number of frames to delay outgoing led updates + LinearColorSmoothing(LedDevice *ledDevice, double ledUpdateFrequency, int settlingTime, unsigned updateDelay); /// Destructor virtual ~LinearColorSmoothing(); @@ -43,6 +44,13 @@ private slots: void updateLeds(); private: + /** + * Pushes the colors into the output queue and popping the head to the led-device + * + * @param ledColors The colors to queue + */ + void queueColors(const std::vector & ledColors); + /// The led device LedDevice * _ledDevice; @@ -66,4 +74,10 @@ private: /// The previously written led data std::vector _previousValues; + + /** The number of updates to keep in the output queue (delayed) before being output */ + const unsigned _outputDelay; + /** The output queue */ + std::list > _outputQueue; + }; diff --git a/libsrc/jsonserver/JsonClientConnection.cpp b/libsrc/jsonserver/JsonClientConnection.cpp index d478a4d2..87fb7c1f 100644 --- a/libsrc/jsonserver/JsonClientConnection.cpp +++ b/libsrc/jsonserver/JsonClientConnection.cpp @@ -10,6 +10,8 @@ // Qt includes #include #include +#include +#include // hyperion util includes #include @@ -25,7 +27,8 @@ JsonClientConnection::JsonClientConnection(QTcpSocket *socket, Hyperion * hyperi _socket(socket), _imageProcessor(ImageProcessorFactory::getInstance().newImageProcessor()), _hyperion(hyperion), - _receiveBuffer() + _receiveBuffer(), + _webSocketHandshakeDone(false) { // connect internal signals and slots connect(_socket, SIGNAL(disconnected()), this, SLOT(socketClosed())); @@ -41,26 +44,164 @@ JsonClientConnection::~JsonClientConnection() void JsonClientConnection::readData() { _receiveBuffer += _socket->readAll(); - - int bytes = _receiveBuffer.indexOf('\n') + 1; - while(bytes > 0) + + if (_webSocketHandshakeDone) { - // create message string - std::string message(_receiveBuffer.data(), bytes); + // websocket mode, data frame + handleWebSocketFrame(); + } else + { + // might be a handshake request or raw socket data + if(_receiveBuffer.contains("Upgrade: websocket")) + { + doWebSocketHandshake(); + } else + { + // raw socket data, handling as usual + int bytes = _receiveBuffer.indexOf('\n') + 1; + while(bytes > 0) + { + // create message string + std::string message(_receiveBuffer.data(), bytes); - // remove message data from buffer - _receiveBuffer = _receiveBuffer.mid(bytes); + // remove message data from buffer + _receiveBuffer = _receiveBuffer.mid(bytes); - // handle message - handleMessage(message); + // handle message + handleMessage(message); - // try too look up '\n' again - bytes = _receiveBuffer.indexOf('\n') + 1; + // try too look up '\n' again + bytes = _receiveBuffer.indexOf('\n') + 1; + } + } } } +void JsonClientConnection::handleWebSocketFrame() +{ + if ((_receiveBuffer.at(0) & 0x80) == 0x80) + { + // final bit found, frame complete + quint8 * maskKey = NULL; + quint8 opCode = _receiveBuffer.at(0) & 0x0F; + bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; + quint64 payloadLength = _receiveBuffer.at(1) & 0x7F; + quint32 index = 2; + + switch (payloadLength) + { + case 126: + payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); + index += 2; + break; + case 127: + payloadLength = 0; + for (uint i=0; i < 8; i++) { + payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); + } + index += 8; + break; + default: + break; + } + + if (isMasked) + { + // if the data is masked we need to get the key for unmasking + maskKey = new quint8[4]; + for (uint i=0; i < 4; i++) + { + maskKey[i] = _receiveBuffer.at(index + i); + } + index += 4; + } + + // check the type of data frame + switch (opCode) + { + case 0x01: + { + // frame contains text, extract it + QByteArray result = _receiveBuffer.mid(index, payloadLength); + _receiveBuffer.clear(); + + // unmask data if necessary + if (isMasked) + { + for (uint i=0; i < payloadLength; i++) + { + result[i] = (result[i] ^ maskKey[i % 4]); + } + if (maskKey != NULL) + { + delete[] maskKey; + maskKey = NULL; + } + } + + handleMessage(QString(result).toStdString()); + } + break; + case 0x08: + { + // close request, confirm + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } + break; + case 0x09: + { + // ping received, send pong + quint8 pong[] = {0x0A, 0}; + _socket->write((const char*)pong, 2); + _socket->flush(); + } + break; + } + } else + { + std::cout << "Someone is sending very big messages over several frames... it's not supported yet" << std::endl; + quint8 close[] = {0x88, 0}; + _socket->write((const char*)close, 2); + _socket->flush(); + _socket->close(); + } +} + +void JsonClientConnection::doWebSocketHandshake() +{ + // http header, might not be a very reliable check... + std::cout << "Websocket handshake" << std::endl; + + // get the key to prepare an answer + int start = _receiveBuffer.indexOf("Sec-WebSocket-Key") + 19; + std::string value(_receiveBuffer.mid(start, _receiveBuffer.indexOf("\r\n", start) - start).data()); + _receiveBuffer.clear(); + + // must be always appended + value += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + // generate sha1 hash + QByteArray hash = QCryptographicHash::hash(value.c_str(), QCryptographicHash::Sha1); + + // prepare an answer + std::ostringstream h; + h << "HTTP/1.1 101 Switching Protocols\r\n" << + "Upgrade: websocket\r\n" << + "Connection: Upgrade\r\n" << + "Sec-WebSocket-Accept: " << QString(hash.toBase64()).toStdString() << "\r\n\r\n"; + + _socket->write(h.str().c_str()); + _socket->flush(); + // we are in WebSocket mode, data frames should follow next + _webSocketHandshakeDone = true; +} + void JsonClientConnection::socketClosed() { + _webSocketHandshakeDone = false; emit connectionClosed(this); } @@ -205,6 +346,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &) Json::Value result; result["success"] = true; Json::Value & info = result["info"]; + + // add host name for remote clients + info["hostname"] = QHostInfo::localHostName().toStdString(); // collect priority information Json::Value & priorities = info["priorities"] = Json::Value(Json::arrayValue); @@ -362,7 +506,32 @@ void JsonClientConnection::sendMessage(const Json::Value &message) { Json::FastWriter writer; std::string serializedReply = writer.write(message); - _socket->write(serializedReply.data(), serializedReply.length()); + + if (!_webSocketHandshakeDone) + { + // raw tcp socket mode + _socket->write(serializedReply.data(), serializedReply.length()); + } else + { + // websocket mode + quint32 size = serializedReply.length(); + + // prepare data frame + QByteArray response; + response.append(0x81); + if (size > 125) + { + response.append(0x7E); + response.append((size >> 8) & 0xFF); + response.append(size & 0xFF); + } else { + response.append(size); + } + + response.append(serializedReply.c_str(), serializedReply.length()); + + _socket->write(response.data(), response.length()); + } } void JsonClientConnection::sendSuccessReply() diff --git a/libsrc/jsonserver/JsonClientConnection.h b/libsrc/jsonserver/JsonClientConnection.h index dc80deba..6575388a 100644 --- a/libsrc/jsonserver/JsonClientConnection.h +++ b/libsrc/jsonserver/JsonClientConnection.h @@ -136,6 +136,16 @@ private: /// @param error String describing the error /// void sendErrorReply(const std::string & error); + + /// + /// Do handshake for a websocket connection + /// + void doWebSocketHandshake(); + + /// + /// Handle incoming websocket data frame + /// + void handleWebSocketFrame(); private: /// @@ -161,4 +171,7 @@ private: /// The buffer used for reading data from the socket QByteArray _receiveBuffer; + + /// used for WebSocket detection and connection handling + bool _webSocketHandshakeDone; }; diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index 441618d2..19f1d96e 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -57,6 +57,7 @@ if(ENABLE_SPIDEV) ${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.h ${CURRENT_SOURCE_DIR}/LedDeviceP9813.h ${CURRENT_SOURCE_DIR}/LedDeviceWs2801.h + ${CURRENT_SOURCE_DIR}/LedDeviceAPA102.h ) SET(Leddevice_SOURCES ${Leddevice_SOURCES} @@ -65,9 +66,21 @@ if(ENABLE_SPIDEV) ${CURRENT_SOURCE_DIR}/LedDeviceLpd8806.cpp ${CURRENT_SOURCE_DIR}/LedDeviceP9813.cpp ${CURRENT_SOURCE_DIR}/LedDeviceWs2801.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceAPA102.cpp ) endif(ENABLE_SPIDEV) +if(ENABLE_WS2812BPWM) +SET(Leddevice_HEADERS + ${Leddevice_HEADERS} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.h + ) +SET(Leddevice_SOURCES + ${Leddevice_SOURCES} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.cpp +) +endif(ENABLE_WS2812BPWM) + if(ENABLE_TINKERFORGE) SET(Leddevice_HEADERS ${Leddevice_HEADERS} diff --git a/libsrc/leddevice/LedDeviceAPA102.cpp b/libsrc/leddevice/LedDeviceAPA102.cpp new file mode 100644 index 00000000..2a2f5e8c --- /dev/null +++ b/libsrc/leddevice/LedDeviceAPA102.cpp @@ -0,0 +1,42 @@ + +// STL includes +#include +#include +#include + +// Linux includes +#include +#include + +// hyperion local includes +#include "LedDeviceAPA102.h" + +LedDeviceAPA102::LedDeviceAPA102(const std::string& outputDevice, const unsigned baudrate) : + LedSpiDevice(outputDevice, baudrate, 500000), + _ledBuffer(0) +{ + // empty +} + +int LedDeviceAPA102::write(const std::vector &ledValues) +{ + const unsigned int mLedCount = (ledValues.size() * 4) + 8; + if(_ledBuffer.size() != mLedCount){ + _ledBuffer.resize(mLedCount, 0x00); + } + + for (unsigned iLed=1; iLed<=ledValues.size(); ++iLed) { + const ColorRgb& rgb = ledValues[iLed]; + _ledBuffer[iLed*4] = 0xFF; + _ledBuffer[iLed*4+1] = rgb.red; + _ledBuffer[iLed*4+2] = rgb.green; + _ledBuffer[iLed*4+3] = rgb.blue; + } + + return writeBytes(_ledBuffer.size(), _ledBuffer.data()); +} + +int LedDeviceAPA102::switchOff() +{ + return write(std::vector(_ledBuffer.size(), ColorRgb{0,0,0})); +} diff --git a/libsrc/leddevice/LedDeviceAPA102.h b/libsrc/leddevice/LedDeviceAPA102.h new file mode 100644 index 00000000..3cc4e518 --- /dev/null +++ b/libsrc/leddevice/LedDeviceAPA102.h @@ -0,0 +1,42 @@ +#pragma once + +// STL includes +#include + +// hyperion incluse +#include "LedSpiDevice.h" + +/// +/// Implementation of the LedDevice interface for writing to APA102 led device. +/// +/// APA102 is +/// +class LedDeviceAPA102 : public LedSpiDevice +{ +public: + /// + /// Constructs the LedDevice for a string containing leds of the type APA102 + /// + /// @param outputDevice The name of the output device (eg '/etc/SpiDev.0.0') + /// @param baudrate The used baudrate for writing to the output device + /// + LedDeviceAPA102(const std::string& outputDevice, + const unsigned baudrate); + + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector &ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + + /// The buffer containing the packed RGB values + std::vector _ledBuffer; + +}; diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index c7797c3d..3208b393 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -14,6 +14,7 @@ #include "LedDeviceLpd8806.h" #include "LedDeviceP9813.h" #include "LedDeviceWs2801.h" + #include "LedDeviceAPA102.h" #endif #ifdef ENABLE_TINKERFORGE @@ -31,6 +32,10 @@ #include "LedDevicePhilipsHue.h" #include "LedDeviceTpm2.h" +#ifdef ENABLE_WS2812BPWM + #include "LedDeviceWS2812b.h" +#endif + LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { std::cout << "Device configuration: " << deviceConfig << std::endl; @@ -82,6 +87,16 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) device = deviceP9813; } + else if (type == "apa102") + { + const std::string output = deviceConfig["output"].asString(); + const unsigned rate = deviceConfig["rate"].asInt(); + + LedDeviceAPA102* deviceAPA102 = new LedDeviceAPA102(output, rate); + deviceAPA102->open(); + + device = deviceAPA102; + } else if (type == "ws2801" || type == "lightberry") { const std::string output = deviceConfig["output"].asString(); @@ -178,10 +193,17 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) const std::string output = deviceConfig["output"].asString(); const unsigned rate = deviceConfig["rate"].asInt(); - LedDeviceTpm2* deviceTpm2 = new LedDeviceTpm2(output, rate); + LedDeviceTpm2 * deviceTpm2 = new LedDeviceTpm2(output, rate); deviceTpm2->open(); device = deviceTpm2; } +#ifdef ENABLE_WS2812BPWM + else if (type == "ws2812b") + { + LedDeviceWS2812b * ledDeviceWS2812b = new LedDeviceWS2812b(); + device = ledDeviceWS2812b; + } +#endif else { std::cout << "Unable to create device " << type << std::endl; diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp new file mode 100644 index 00000000..67a10947 --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -0,0 +1,883 @@ +// For license and other informations see LedDeviceWS2812b.h +// To activate: use led device "ws2812s" in the hyperion configuration + +// STL includes +#include +#include +#include +#include + +// Linux includes +#include +#include +#include +#include +//#include +//#include + +#ifdef BENCHMARK + #include +#endif + +// hyperion local includes +#include "LedDeviceWS2812b.h" + +// ==== Defines and Vars ==== + +// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!) +// These will be "memory mapped" into virtual RAM so that they can be written and read directly. +// ------------------------------------------------------------------------------------------------- +#define DMA_BASE 0x20007000 +#define DMA_LEN 0x24 +#define PWM_BASE 0x2020C000 +#define PWM_LEN 0x28 +#define CLK_BASE 0x20101000 +#define CLK_LEN 0xA8 +#define GPIO_BASE 0x20200000 +#define GPIO_LEN 0xB4 + +// GPIO +// ------------------------------------------------------------------------------------------------- +#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved) +#define GPFSEL1 0x20200004 // Pins 10-19 +#define GPFSEL2 0x20200008 // Pins 20-29 +#define GPFSEL3 0x2020000C // Pins 30-39 +#define GPFSEL4 0x20200010 // Pins 40-49 +#define GPFSEL5 0x20200014 // Pins 50-53 +#define GPSET0 0x2020001C // Set (turn on) pin +#define GPCLR0 0x20200028 // Clear (turn off) pin +#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control +#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31 +#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53 + +// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom! +// ------------------------------------------------------------------------------------------------- +#define PWM_CLK_CNTL 40 // Control (on/off) +#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part) + +// PWM Register Addresses (page 141) +// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but +// the pointers we use in this program are in words (32 bits). Buss' original defines are in +// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it +// matches the numbers supplied in the guide. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL 0x00 // Control Register +#define PWM_STA (0x04 / 4) // Status Register +#define PWM_DMAC (0x08 / 4) // DMA Control Register +#define PWM_RNG1 (0x10 / 4) // Channel 1 Range +#define PWM_DAT1 (0x14 / 4) // Channel 1 Data +#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active) +#define PWM_RNG2 (0x20 / 4) // Channel 2 Range +#define PWM_DAT2 (0x24 / 4) // Channel 2 Data + +// PWM_CTL register bit offsets +// Note: Don't use MSEN1/2 for this purpose. It will screw things up. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO. +#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO +#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM +#define PWM_CTL_CLRF1 6 // Clear FIFO +#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO. +#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO +#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM + +// PWM_STA register bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_STA_STA4 12 // Channel 4 State +#define PWM_STA_STA3 11 // Channel 3 State +#define PWM_STA_STA2 10 // Channel 2 State +#define PWM_STA_STA1 9 // Channel 1 State +#define PWM_STA_BERR 8 // Bus Error +#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4 +#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3 +#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2 +#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1 +#define PWM_STA_RERR1 3 // FIFO Read Error +#define PWM_STA_WERR1 2 // FIFO Write Error +#define PWM_STA_EMPT1 1 // FIFO Empty +#define PWM_STA_FULL1 0 // FIFO Full + +// PWM_DMAC bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled. +#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7. +#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7. + +// PWM_RNG1, PWM_RNG2 +// -------------------------------------------------------------------------------------------------- +// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period +// of length defined in these registers. In serial mode, serialized data is sent within the +// same period. The value is normally 32. If less, data will be truncated. If more, data will +// be padded with zeros. + +// DAT1, DAT2 +// -------------------------------------------------------------------------------------------------- +// NOTE: These registers are not useful for our purposes - we will use the FIFO instead! +// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many +// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode, +// defines a 32-bit word to be transmitted. + +// FIF1 +// -------------------------------------------------------------------------------------------------- +// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write +// it 16 times, it will fill the FIFO.) +// See also: PWM_STA_EMPT1 (FIFO empty) +// PWM_STA_FULL1 (FIFO full) +// PWM_CTL_CLRF1 (Clear FIFO) + +// DMA +// -------------------------------------------------------------------------------------------------- +// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers) +#define DMA_CS (0x00 / 4) // Control & Status register +#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!) +#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB) +#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address. +#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address. +#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB +#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB +#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB +#define DMA_DEBUG (0x20 / 4) // Debug settings + +// DMA Control & Status register bit offsets +#define DMA_CS_RESET 31 // Reset the controller for this channel +#define DMA_CS_ABORT 30 // Set to abort transfer +#define DMA_CS_DISDEBUG 29 // Disable debug pause signal +#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes +#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7 +#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7 +#define DMA_CS_ERROR 8 // Set when there's been an error +#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted +#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive +#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.) +#define DMA_CS_DREQ 3 // Set when DREQ line is high +#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end +#define DMA_CS_END 1 // Set when the current control block is finished +#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0) +// Default CS word +#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \ + (8 << DMA_CS_PRIORITY) | \ + (1 << DMA_CS_WAIT_FOR) + +// DREQ lines (page 61, most DREQs omitted) +#define DMA_DREQ_ALWAYS 0 +#define DMA_DREQ_PCM_TX 2 +#define DMA_DREQ_PCM_RX 3 +#define DMA_DREQ_PWM 5 +#define DMA_DREQ_SPI_TX 6 +#define DMA_DREQ_SPI_RX 7 +#define DMA_DREQ_BSC_TX 8 +#define DMA_DREQ_BSC_RX 9 + +// DMA Transfer Information register bit offsets +// We don't write DMA_TI directly. It's populated from the TI field in a control block. +#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts +#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write +#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5) +#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12) +#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill) +#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads +#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read +#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes +#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes +#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read +#define DMA_TI_WAIT_RESP 3 // Wait for write response +#define DMA_TI_TDMODE 1 // 2D striding mode +#define DMA_TI_INTEN 0 // Interrupt enable +// Default TI word +#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \ + (1 << DMA_TI_SRC_INC) | \ + (1 << DMA_TI_DEST_DREQ) | \ + (1 << DMA_TI_WAIT_RESP) | \ + (1 << DMA_TI_INTEN) | \ + (DMA_DREQ_PWM << DMA_TI_PERMAP) + +// DMA Debug register bit offsets +#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite" +#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25) +#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16) +#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8) +#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4) +#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting) +#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting) +#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting) + + + +#define PAGE_SIZE 4096 // Size of a RAM page to be allocated +#define PAGE_SHIFT 12 // This is used for address translation +#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT) + +#define SETBIT(word, bit) word |= 1< &ledValues) +{ +#ifdef BENCHMARK + timespec timeStart; + timespec timeEnd; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeStart); +#endif + + mLedCount = ledValues.size(); + + // Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform + unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern + unsigned int wireBit = 1; // Holds the current bit we will set in PWMWaveform, start with 1 and skip the other two for speed + + // Copy PWM waveform to DMA's data buffer + //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); + struct control_data_s *ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) + { + cbp->length = NUM_DATA_WORDS * 4; + mLedCount = (NUM_DATA_WORDS - 1) / 2.25; + } + +#ifdef WS2812_ASM_OPTI + unsigned int startbitPattern = 0x40000000; // = 0100 0000 0000 0000 0000 0000 0000 0000 pattern +#endif + + + for(size_t i=0; i=0; j--) { +#ifdef WS2812_ASM_OPTI + // Fetch word the bit is in + unsigned int wordOffset = (int)(wireBit / 32); + wireBit +=3; + + if (colorBits & (1 << j)) { + PWMWaveform[wordOffset] |= startbitPattern; + } else { + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + } + + startbitPattern = arm_ror_imm(startbitPattern, 3); +#else + unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed + setPWMBit(wireBit, colorBit); + wireBit +=3; +#endif + /* old code for better understanding + switch(colorBit) { + case 1: + //wireBits = 0b110; // High, High, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + break; + case 0: + //wireBits = 0b100; // High, Low, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + setPWMBit(wireBit++, 0); + break; + }*/ + } + } + +#ifdef WS2812_ASM_OPTI + // calculate the bits manually since it is not needed with asm + //wireBit += mLedCount * 24 *3; + //printf(" %d\n", wireBit); +#endif + //remove one to undo optimization + wireBit --; + +#ifdef WS2812_ASM_OPTI + int rest = 32 - wireBit % 32; // 64: 32 - used Bits + startbitPattern = (1 << (rest-1)); // set new bitpattern to start at the benigining of one bit (3 bit in wave form) + rest += 32; // add one int extra for pwm + +// printBinary(startbitPattern, 32); +// printf(" startbit\n"); + + unsigned int oldwireBitValue = wireBit; + unsigned int oldbitPattern = startbitPattern; + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3) + { + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + startbitPattern = arm_ror_imm(startbitPattern, 3); + } + +#else + // fill up the bytes + int rest = 32 - wireBit % 32 + 32; // 64: 32 - used Bits + 32 (one int extra for pwm) + unsigned int oldwireBitValue = wireBit; + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3) + { + setPWMBit(wireBit, 0); + wireBit += 3; + } +#endif + + memcpy ( ctl->sample, PWMWaveform, cbp->length ); + + // Enable DMA and PWM engines, which should now send the data + startTransfer(); + + // restore bit pattern + wireBit = oldwireBitValue; + +#ifdef WS2812_ASM_OPTI + startbitPattern = oldbitPattern; + for (int i = 0; i < rest; i += 3) + { + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] |= startbitPattern; + startbitPattern = arm_ror_imm(startbitPattern, 3); + } +#else + for (int i = 0; i < rest; i += 3) + { + setPWMBit(wireBit, 1); + wireBit += 3; + } +#endif + +#ifdef BENCHMARK + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeEnd); + timespec result; + + result.tv_sec = timeEnd.tv_sec - timeStart.tv_sec; + result.tv_nsec = timeEnd.tv_nsec - timeStart.tv_nsec; + if (result.tv_nsec < 0) + { + result.tv_nsec = 1e9 - result.tv_nsec; + result.tv_sec -= 1; + } + runCount ++; + combinedNseconds += result.tv_nsec; + shortestNseconds = result.tv_nsec < shortestNseconds ? result.tv_nsec : shortestNseconds; +#endif + return 0; +} + +int LedDeviceWS2812b::switchOff() +{ + return write(std::vector(mLedCount, ColorRgb{0,0,0})); +} + +LedDeviceWS2812b::~LedDeviceWS2812b() +{ + // Exit cleanly, freeing memory and stopping the DMA & PWM engines + terminate(0); +#ifdef BENCHMARK + printf("WS2812b Benchmark results: Runs %d - Avarage %lu (n) - Minimum %ld (n)\n", + runCount, (runCount > 0 ? combinedNseconds / runCount : 0), shortestNseconds); +#endif +} + + +// ================================================================================================= +// ________ .__ +// / _____/ ____ ____ ________________ | | +// / \ ____/ __ \ / \_/ __ \_ __ \__ \ | | +// \ \_\ \ ___/| | \ ___/| | \// __ \| |__ +// \______ /\___ >___| /\___ >__| (____ /____/ +// \/ \/ \/ \/ \/ +// ================================================================================================= + +// Convenience functions +// -------------------------------------------------------------------------------------------------- +// Print some bits of a binary number (2nd arg is how many bits) +void LedDeviceWS2812b::printBinary(unsigned int i, unsigned int bits) +{ + int x; + for(x=bits-1; x>=0; x--) + { + printf("%d", (i & (1 << x)) ? 1 : 0); + if(x % 16 == 0 && x > 0) + { + printf(" "); + } + else if(x % 4 == 0 && x > 0) + { + printf(":"); + } + } +} + +// Reverse the bits in a word +unsigned int reverseWord(unsigned int word) +{ + unsigned int output = 0; + //unsigned char bit; + int i; + for(i=0; i<32; i++) + { + output |= word & (1 << i) ? 1 : 0; + if(i<31) + { + output <<= 1; + } + } + return output; +} + +// Shutdown functions +// -------------------------------------------------------------------------------------------------- +void LedDeviceWS2812b::terminate(int dummy) { + // Shut down the DMA controller + if(dma_reg) + { + CLRBIT(dma_reg[DMA_CS], DMA_CS_ACTIVE); + usleep(100); + SETBIT(dma_reg[DMA_CS], DMA_CS_RESET); + usleep(100); + } + + // Shut down PWM + if(pwm_reg) + { + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); + usleep(100); + pwm_reg[PWM_CTL] = (1 << PWM_CTL_CLRF1); + } + + // Free the allocated memory + if(page_map != 0) + { + free(page_map); + } +} + +void LedDeviceWS2812b::fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + terminate(0); +} + + +// Memory management +// -------------------------------------------------------------------------------------------------- +// Translate from virtual address to physical +unsigned int LedDeviceWS2812b::mem_virt_to_phys(void *virt) +{ + unsigned int offset = (uint8_t *)virt - virtbase; + return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); +} + +// Translate from physical address to virtual +unsigned int LedDeviceWS2812b::mem_phys_to_virt(uint32_t phys) +{ + unsigned int pg_offset = phys & (PAGE_SIZE - 1); + unsigned int pg_addr = phys - pg_offset; + + for (unsigned int i = 0; i < NUM_PAGES; i++) + { + if (page_map[i].physaddr == pg_addr) + { + return (uint32_t)virtbase + i * PAGE_SIZE + pg_offset; + } + } + fatal("Failed to reverse map phys addr %08x\n", phys); + + return 0; +} + +// Map a peripheral's IO memory into our virtual memory, so we can read/write it directly +void * LedDeviceWS2812b::map_peripheral(uint32_t base, uint32_t len) +{ + int fd = open("/dev/mem", O_RDWR); + void * vaddr; + + if (fd < 0) + { + fatal("Failed to open /dev/mem: %m\n"); + } + vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base); + if (vaddr == MAP_FAILED) + { + fatal("Failed to map peripheral at 0x%08x: %m\n", base); + } + close(fd); + + return vaddr; +} + +// Zero out the PWM waveform buffer +void LedDeviceWS2812b::clearPWMBuffer() +{ + memset(PWMWaveform, 0, NUM_DATA_WORDS * 4); // Times four because memset deals in bytes. +} + +// Set an individual bit in the PWM output array, accounting for word boundaries +// The (31 - bitIdx) is so that we write the data backwards, correcting its endianness +// This means getPWMBit will return something other than what was written, so it would be nice +// if the logic that calls this function would figure it out instead. (However, that's trickier) +void LedDeviceWS2812b::setPWMBit(unsigned int bitPos, unsigned char bit) +{ + // Fetch word the bit is in + unsigned int wordOffset = (int)(bitPos / 32); + unsigned int bitIdx = bitPos - (wordOffset * 32); + + switch(bit) + { + case 1: + PWMWaveform[wordOffset] |= (1 << (31 - bitIdx)); + break; + case 0: + PWMWaveform[wordOffset] &= ~(1 << (31 - bitIdx)); + break; + } +} + +// ==== Init Hardware ==== +void LedDeviceWS2812b::initHardware() +{ + int pid; + int fd; + char pagemap_fn[64]; + + // Clear the PWM buffer + // --------------------------------------------------------------- + clearPWMBuffer(); + + // Set up peripheral access + // --------------------------------------------------------------- + dma_reg = (unsigned int *) map_peripheral(DMA_BASE, DMA_LEN); + dma_reg += 0x000; + pwm_reg = (unsigned int *) map_peripheral(PWM_BASE, PWM_LEN); + clk_reg = (unsigned int *) map_peripheral(CLK_BASE, CLK_LEN); + gpio_reg = (unsigned int *) map_peripheral(GPIO_BASE, GPIO_LEN); + + + // Set PWM alternate function for GPIO18 + // --------------------------------------------------------------- + //gpio_reg[1] &= ~(7 << 24); + //usleep(100); + //gpio_reg[1] |= (2 << 24); + //usleep(100); + SET_GPIO_ALT(18, 5); + + + // Allocate memory for the DMA control block & data to be sent + // --------------------------------------------------------------- + virtbase = (uint8_t *) mmap( + NULL, // Address + NUM_PAGES * PAGE_SIZE, // Length + PROT_READ | PROT_WRITE, // Protection + MAP_SHARED | // Shared + MAP_ANONYMOUS | // Not file-based, init contents to 0 + MAP_NORESERVE | // Don't reserve swap space + MAP_LOCKED, // Lock in RAM (don't swap) + -1, // File descriptor + 0); // Offset + + if (virtbase == MAP_FAILED) + { + fatal("Failed to mmap physical pages: %m\n"); + return; + } + + if ((unsigned long)virtbase & (PAGE_SIZE-1)) + { + fatal("Virtual address is not page aligned\n"); + return; + } + + // Allocate page map (pointers to the control block(s) and data for each CB + page_map = (page_map_t *) malloc(NUM_PAGES * sizeof(*page_map)); + if (page_map == 0) + { + fatal("Failed to malloc page_map: %m\n"); + return; + } + + // Use /proc/self/pagemap to figure out the mapping between virtual and physical addresses + pid = getpid(); + sprintf(pagemap_fn, "/proc/%d/pagemap", pid); + fd = open(pagemap_fn, O_RDONLY); + + if (fd < 0) + { + fatal("Failed to open %s: %m\n", pagemap_fn); + } + + off_t newOffset = (unsigned long)virtbase >> 9; + if (lseek(fd, newOffset, SEEK_SET) != newOffset) + { + fatal("Failed to seek on %s: %m\n", pagemap_fn); + } + + printf("Page map: %d pages\n", NUM_PAGES); + for (unsigned int i = 0; i < NUM_PAGES; i++) + { + uint64_t pfn; + page_map[i].virtaddr = virtbase + i * PAGE_SIZE; + + // Following line forces page to be allocated + // (Note: Copied directly from Hirst's code... page_map[i].virtaddr[0] was just set...?) + page_map[i].virtaddr[0] = 0; + + if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) { + fatal("Failed to read %s: %m\n", pagemap_fn); + } + + if (((pfn >> 55) & 0xfbf) != 0x10c) { // pagemap bits: https://www.kernel.org/doc/Documentation/vm/pagemap.txt + fatal("Page %d not present (pfn 0x%016llx)\n", i, pfn); + } + + page_map[i].physaddr = (unsigned int)pfn << PAGE_SHIFT | 0x40000000; + //printf("Page map #%2d: virtual %8p ==> physical 0x%08x [0x%016llx]\n", i, page_map[i].virtaddr, page_map[i].physaddr, pfn); + } + + + // Set up control block + // --------------------------------------------------------------- + struct control_data_s *ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + // FIXME: Change this to use DEFINEs + unsigned int phys_pwm_fifo_addr = 0x7e20c000 + 0x18; + + // No wide bursts, source increment, dest DREQ on line 5, wait for response, enable interrupt + cbp->info = DMA_TI_CONFIGWORD; + + // Source is our allocated memory + cbp->src = mem_virt_to_phys(ctl->sample); + + // Destination is the PWM controller + cbp->dst = phys_pwm_fifo_addr; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) + { + cbp->length = NUM_DATA_WORDS * 4; + } + + // We don't use striding + cbp->stride = 0; + + // These are reserved + cbp->pad[0] = 0; + cbp->pad[1] = 0; + + // Pointer to next block - 0 shuts down the DMA channel when transfer is complete + cbp->next = 0; + + // Stop any existing DMA transfers + // --------------------------------------------------------------- + dma_reg[DMA_CS] |= (1 << DMA_CS_ABORT); + usleep(100); + dma_reg[DMA_CS] = (1 << DMA_CS_RESET); + usleep(100); + + + // PWM Clock + // --------------------------------------------------------------- + // Kill the clock + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000000 | (1 << 5); + usleep(100); + + // Disable DMA requests + CLRBIT(pwm_reg[PWM_DMAC], PWM_DMAC_ENAB); + usleep(100); + + // The fractional part is quantized to a range of 0-1024, so multiply the decimal part by 1024. + // E.g., 0.25 * 1024 = 256. + // So, if you want a divisor of 400.5, set idiv to 400 and fdiv to 512. + unsigned int idiv = 400; + unsigned short fdiv = 0; // Should be 16 bits, but the value must be <= 1024 + clk_reg[PWM_CLK_DIV] = 0x5A000000 | (idiv << 12) | fdiv; // Set clock multiplier + usleep(100); + + // Enable the clock. Next-to-last digit means "enable clock". Last digit is 1 (oscillator), + // 4 (PLLA), 5 (PLLC), or 6 (PLLD) (according to the docs) although PLLA doesn't seem to work. + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000015; + usleep(100); + + + // PWM + // --------------------------------------------------------------- + // Clear any preexisting crap from the control & status register + pwm_reg[PWM_CTL] = 0; + + // Set transmission range (32 bytes, or 1 word) + // <32: Truncate. >32: Pad with SBIT1. As it happens, 32 is perfect. + pwm_reg[PWM_RNG1] = 32; + usleep(100); + + // Send DMA requests to fill the FIFO + pwm_reg[PWM_DMAC] = + (1 << PWM_DMAC_ENAB) | + (8 << PWM_DMAC_PANIC) | + (8 << PWM_DMAC_DREQ); + usleep(1000); + + // Clear the FIFO + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_CLRF1); + usleep(100); + + // Don't repeat last FIFO contents if it runs dry + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_RPTL1); + usleep(100); + + // Silence (default) bit is 0 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_SBIT1); + usleep(100); + + // Polarity = default (low = 0, high = 1) + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_POLA1); + usleep(100); + + // Enable serializer mode + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_MODE1); + usleep(100); + + // Use FIFO rather than DAT1 + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_USEF1); + usleep(100); + + // Disable MSEN1 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_MSEN1); + usleep(100); + + + // DMA + // --------------------------------------------------------------- + // Raise an interrupt when transfer is complete, which will set the INT flag in the CS register + SETBIT(dma_reg[DMA_CS], DMA_CS_INT); + usleep(100); + + // Clear the END flag (by setting it - this is a "write 1 to clear", or W1C, bit) + SETBIT(dma_reg[DMA_CS], DMA_CS_END); + usleep(100); + + // Send the physical address of the control block into the DMA controller + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + usleep(100); + + // Clear error flags, if any (these are also W1C bits) + // FIXME: Use a define instead of this + dma_reg[DMA_DEBUG] = 7; + usleep(100); +} + +// Begin the transfer +void LedDeviceWS2812b::startTransfer() +{ + // Enable DMA + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(((struct control_data_s *) virtbase)->cb); + dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE); + usleep(100); + + // Enable PWM + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); +} diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h new file mode 100644 index 00000000..d945e20d --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -0,0 +1,218 @@ +#ifndef LEDDEVICEWS2812B_H_ +#define LEDDEVICEWS2812B_H_ + +#pragma once + + +// Set tabs to 4 spaces. + +// ================================================================================================= +// +// __ __ _________________ ______ ____________ ____________________.__ +// / \ / \/ _____/\_____ \ / __ \/_ \_____ \ \______ \______ \__| +// \ \/\/ /\_____ \ / ____/ > < | |/ ____/ | _/| ___/ | +// \ / / \/ \/ -- \| / \ | | \| | | | +// \__/\ / /_______ /\_______ \______ /|___\_______ \ |____|_ /|____| |__| +// \/ \/ \/ \/ \/ \/ +// +// WS2812 NeoPixel driver +// Based on code by Richard G. Hirst and others +// Adapted for the WS2812 by 626Pilot, April/May 2014 +// See: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812 +// Version: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812/blob/1d43407d9e6eba19bff24330bc09a27963b55751/ws2812-RPi.c +// Huge ASCII art section labels are from http://patorjk.com/software/taag/ +// +// LED driver adaptation by Kammerjaeger () +// mostly code removed that was not needed +// +// License: GPL +// +// You are using this at your OWN RISK. I believe this software is reasonably safe to use (aside +// from the intrinsic risk to those who are photosensitive - see below), although I can't be certain +// that it won't trash your hardware or cause property damage. +// +// Speaking of risk, WS2812 pixels are bright enough to cause eye pain and (for all I know) possibly +// retina damage when run at full strength. It's a good idea to set the brightness at 0.2 or so for +// direct viewing (whether you're looking directly at the pixels or not), or to put some diffuse +// material between you and the LEDs. +// +// PHOTOSENSITIVITY WARNING: +// Patterns of light and darkness (stationary or moving), flashing lights, patterns and backgrounds +// on screens, and the like, may cause epilleptic seizures in some people. This is a danger EVEN IF +// THE PERSON (WHICH MAY BE *YOU*) HAS NEVER KNOWINGLY HAD A PHOTOSENSITIVE EPISODE BEFORE. It's up +// to you to learn the warning signs, but symptoms may include dizziness, nausea, vision changes, +// convlusions, disorientation, involuntary movements, and eye twitching. (This list is not +// necessarily exhaustive.) +// +// NEOPIXEL BEST PRACTICES: https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices +// +// Connections: +// Positive to Raspberry Pi's 3.3v, for better separation connect only ground and data directly +// (5v can be used then without a problem, at least it worked for me, Kammerjaeger) +// Negative to Raspberry Pi's ground +// Data to GPIO18 (Pin 12) (through a resistor, which you should know from the Best +// Practices guide!) +// +// Buy WS2812-based stuff from: http://adafruit.com +// +// To activate: use led device "ws2812s" in the hyperion configuration +// (it needs to be root so it can map the peripherals' registers) +// +// ================================================================================================= + +// This is for the WS2812 LEDs. It won't work with the older WS2811s, although it could be modified +// for that without too much trouble. Preliminary driver used Frank Buss' servo driver, but I moved +// to Richard Hirst's memory mapping/access model because his code already works with DMA, and has +// what I think is a slightly cleaner way of accessing the registers: register[name] rather than +// *(register + name). + +// At the time of writing, there's a lot of confusing "PWM DMA" code revolving around simulating +// an FM signal. Usually this is done without properly initializing certain registers, which is +// OK for their purpose, but I needed to be able to transfer actual coherent data and have it wind +// up in a proper state once it was transferred. This has proven to be a somewhat painful task. +// The PWM controller likes to ignore the RPTL1 bit when the data is in a regular, repeating +// pattern. I'M NOT MAKING IT UP! It really does that. It's bizarre. There are lots of other +// strange irregularities as well, which had to be figured out through trial and error. It doesn't +// help that the BCM2835 ARM Peripherals manual contains outright errors and omissions! + +// Many examples of this kind of code have magic numbers in them. If you don't know, a magic number +// is one that either lacks an obvious structure (e.g. 0x2020C000) or purpose. Please don't use +// that stuff in any code you release! All magic numbers found in reference code have been changed +// to DEFINEs. That way, instead of seeing some inscrutable number, you see (e.g.) PWM_CTL. + +// References - BCM2835 ARM Peripherals: +// http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// +// Raspberry Pi low-level peripherals: +// http://elinux.org/RPi_Low-level_peripherals +// +// Richard Hirst's nice, clean code: +// https://github.com/richardghirst/PiBits/blob/master/PiFmDma/PiFmDma.c +// +// PWM clock register: +// http://www.raspberrypi.org/forums/viewtopic.php?t=8467&p=124620 +// +// Simple (because it's in assembly) PWM+DMA setup: +// https://github.com/mikedurso/rpi-projects/blob/master/asm-nyancat/rpi-nyancat.s +// +// Adafruit's NeoPixel driver: +// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp + +// Hyperion includes +#include + +//#define BENCHMARK +#define WS2812_ASM_OPTI + +// The page map contains pointers to memory that we will allocate below. It uses two pointers +// per address. This is because the software (this program) deals only in virtual addresses, +// whereas the DMA controller can only access RAM via physical address. (If that's not confusing +// enough, it writes to peripherals by their bus addresses.) +struct page_map_t +{ + uint8_t *virtaddr; + uint32_t physaddr; +}; + +// Control Block (CB) - this tells the DMA controller what to do. +struct dma_cb_t +{ + unsigned info; // Transfer Information (TI) + unsigned src; // Source address (physical) + unsigned dst; // Destination address (bus) + unsigned length; // Length in bytes (not words!) + unsigned stride; // We don't care about this + unsigned next; // Pointer to next control block + unsigned pad[2]; // These are "reserved" (unused) +}; + +/// +/// Implementation of the LedDevice interface for writing to Ws2801 led device. +/// +class LedDeviceWS2812b : public LedDevice +{ +public: + /// + /// Constructs the LedDevice for a string containing leds of the type WS2812 + LedDeviceWS2812b(); + + ~LedDeviceWS2812b(); + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector &ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + + /// the number of leds (needed when switching off) + size_t mLedCount; + + page_map_t *page_map; // This will hold the page map, which we'll allocate + uint8_t *virtbase; // Pointer to some virtual memory that will be allocated + + volatile unsigned int *pwm_reg; // PWM controller register set + volatile unsigned int *clk_reg; // PWM clock manager register set + volatile unsigned int *dma_reg; // DMA controller register set + volatile unsigned int *gpio_reg; // GPIO pin controller register set + + // Contains arrays of control blocks and their related samples. + // One pixel needs 72 bits (24 bits for the color * 3 to represent them on the wire). + // 768 words = 341.3 pixels + // 1024 words = 455.1 pixels + // The highest I can make this number is 1016. Any higher, and it will start copying garbage to the + // PWM controller. I think it might be because of the virtual->physical memory mapping not being + // contiguous, so *pointer+1016 isn't "next door" to *pointer+1017 for some weird reason. + // However, that's still enough for 451.5 color instructions! If someone has more pixels than that + // to control, they can figure it out. I tried Hirst's message of having one CB per word, which + // seems like it might fix that, but I couldn't figure it out. + #define NUM_DATA_WORDS 1016 + struct control_data_s { + dma_cb_t cb[1]; + uint32_t sample[NUM_DATA_WORDS]; + }; + + //struct control_data_s *ctl; + + // PWM waveform buffer (in words), 16 32-bit words are enough to hold 170 wire bits. + // That's OK if we only transmit from the FIFO, but for DMA, we will use a much larger size. + // 1024 (4096 bytes) should be enough for over 400 elements. It can be bumped up if you need more! + unsigned int PWMWaveform[NUM_DATA_WORDS]; + + void initHardware(); + void startTransfer(); + + void clearPWMBuffer(); + void setPWMBit(unsigned int bitPos, unsigned char bit); + + unsigned int mem_phys_to_virt(uint32_t phys); + unsigned int mem_virt_to_phys(void *virt); + void terminate(int dummy); + void fatal(const char *fmt, ...); + void * map_peripheral(uint32_t base, uint32_t len); + void printBinary(unsigned int i, unsigned int bits); + +#ifdef BENCHMARK + unsigned int runCount; + long combinedNseconds; + long shortestNseconds; +#endif +}; + + + + + + + + + + + + +#endif /* LEDDEVICEWS2812B_H_ */ diff --git a/libsrc/protoserver/CMakeLists.txt b/libsrc/protoserver/CMakeLists.txt index 9e1992a3..6a9b6d1e 100644 --- a/libsrc/protoserver/CMakeLists.txt +++ b/libsrc/protoserver/CMakeLists.txt @@ -4,45 +4,51 @@ set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/protoserver) set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/protoserver) include_directories( - ${CMAKE_CURRENT_BINARY_DIR} - ${PROTOBUF_INCLUDE_DIRS}) + ${CMAKE_CURRENT_BINARY_DIR} + ${PROTOBUF_INCLUDE_DIRS} +) # Group the headers that go through the MOC compiler set(ProtoServer_QT_HEADERS - ${CURRENT_HEADER_DIR}/ProtoServer.h - ${CURRENT_SOURCE_DIR}/ProtoClientConnection.h + ${CURRENT_HEADER_DIR}/ProtoServer.h + ${CURRENT_HEADER_DIR}/ProtoConnection.h + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.h + ${CURRENT_HEADER_DIR}/ProtoConnectionWrapper.h ) set(ProtoServer_HEADERS ) set(ProtoServer_SOURCES - ${CURRENT_SOURCE_DIR}/ProtoServer.cpp - ${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp + ${CURRENT_SOURCE_DIR}/ProtoServer.cpp + ${CURRENT_SOURCE_DIR}/ProtoClientConnection.cpp + ${CURRENT_SOURCE_DIR}/ProtoConnection.cpp + ${CURRENT_SOURCE_DIR}/ProtoConnectionWrapper.cpp ) set(ProtoServer_PROTOS - ${CURRENT_SOURCE_DIR}/message.proto + ${CURRENT_SOURCE_DIR}/message.proto ) protobuf_generate_cpp(ProtoServer_PROTO_SRCS ProtoServer_PROTO_HDRS - ${ProtoServer_PROTOS} + ${ProtoServer_PROTOS} ) qt4_wrap_cpp(ProtoServer_HEADERS_MOC ${ProtoServer_QT_HEADERS}) add_library(protoserver - ${ProtoServer_HEADERS} - ${ProtoServer_QT_HEADERS} - ${ProtoServer_SOURCES} - ${ProtoServer_HEADERS_MOC} - ${ProtoServer_PROTOS} - ${ProtoServer_PROTO_SRCS} - ${ProtoServer_PROTO_HDRS} + ${ProtoServer_HEADERS} + ${ProtoServer_QT_HEADERS} + ${ProtoServer_SOURCES} + ${ProtoServer_HEADERS_MOC} + ${ProtoServer_PROTOS} + ${ProtoServer_PROTO_SRCS} + ${ProtoServer_PROTO_HDRS} ) target_link_libraries(protoserver - hyperion - hyperion-utils - ${PROTOBUF_LIBRARIES} - ${QT_LIBRARIES}) + hyperion + hyperion-utils + protobuf + ${QT_LIBRARIES} +) diff --git a/libsrc/protoserver/ProtoConnection.cpp b/libsrc/protoserver/ProtoConnection.cpp new file mode 100644 index 00000000..d7a6037e --- /dev/null +++ b/libsrc/protoserver/ProtoConnection.cpp @@ -0,0 +1,188 @@ +// stl includes +#include + +// Qt includes +#include + +// protoserver includes +#include "protoserver/ProtoConnection.h" + +ProtoConnection::ProtoConnection(const std::string & a) : + _socket(), + _skipReply(false) +{ + 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()); + } + _host = parts[0]; + + bool ok; + _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()); + } + + // try to connect to host + std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; + connectToHost(); +} + +ProtoConnection::~ProtoConnection() +{ + _socket.close(); +} + +void ProtoConnection::setSkipReply(bool skip) +{ + _skipReply = skip; +} + +void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration) +{ + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::COLOR); + proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest); + colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue); + colorRequest->set_priority(priority); + colorRequest->set_duration(duration); + + // send command message + sendMessage(request); +} + +void ProtoConnection::setImage(const Image &image, int priority, int duration) +{ + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::IMAGE); + proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest); + imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3); + imageRequest->set_imagewidth(image.width()); + imageRequest->set_imageheight(image.height()); + imageRequest->set_priority(priority); + imageRequest->set_duration(duration); + + // send command message + sendMessage(request); +} + +void ProtoConnection::clear(int priority) +{ + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::CLEAR); + proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest); + clearRequest->set_priority(priority); + + // send command message + sendMessage(request); +} + +void ProtoConnection::clearAll() +{ + proto::HyperionRequest request; + request.set_command(proto::HyperionRequest::CLEARALL); + + // send command message + sendMessage(request); +} + +void ProtoConnection::connectToHost() +{ + _socket.connectToHost(_host, _port); + if (_socket.waitForConnected()) { + std::cout << "Connected to Hyperion host" << std::endl; + } +} + +void ProtoConnection::sendMessage(const proto::HyperionRequest &message) +{ + if (_socket.state() == QAbstractSocket::UnconnectedState) + { + std::cout << "Currently disconnected: trying to connect to host" << std::endl; + connectToHost(); + } + + if (_socket.state() != QAbstractSocket::ConnectedState) + { + return; + } + + // We only get here if we are connected + + // serialize message (FastWriter already appends a newline) + std::string serializedMessage = message.SerializeAsString(); + + int length = serializedMessage.size(); + const uint8_t header[] = { + uint8_t((length >> 24) & 0xFF), + uint8_t((length >> 16) & 0xFF), + uint8_t((length >> 8) & 0xFF), + uint8_t((length ) & 0xFF)}; + + // write message + int count = 0; + count += _socket.write(reinterpret_cast(header), 4); + count += _socket.write(reinterpret_cast(serializedMessage.data()), length); + if (!_socket.waitForBytesWritten()) + { + std::cerr << "Error while writing data to host" << std::endl; + return; + } + + if (!_skipReply) + { + // read reply data + QByteArray serializedReply; + length = -1; + while (length < 0 && serializedReply.size() < length+4) + { + // receive reply + if (!_socket.waitForReadyRead()) + { + std::cerr << "Error while reading data from host" << std::endl; + return; + } + + serializedReply += _socket.readAll(); + + if (length < 0 && serializedReply.size() >= 4) + { + // read the message size + length = + ((serializedReply[0]<<24) & 0xFF000000) | + ((serializedReply[1]<<16) & 0x00FF0000) | + ((serializedReply[2]<< 8) & 0x0000FF00) | + ((serializedReply[3] ) & 0x000000FF); + } + } + + // parse reply data + proto::HyperionReply reply; + reply.ParseFromArray(serializedReply.constData()+4, length); + + // parse reply message + parseReply(reply); + } +} + +bool ProtoConnection::parseReply(const proto::HyperionReply &reply) +{ + bool success = false; + + if (!reply.success()) + { + if (reply.has_error()) + { + throw std::runtime_error("Error: " + reply.error()); + } + else + { + throw std::runtime_error("Error: No error info"); + } + } + + return success; +} diff --git a/libsrc/protoserver/ProtoConnectionWrapper.cpp b/libsrc/protoserver/ProtoConnectionWrapper.cpp new file mode 100644 index 00000000..7cf88f43 --- /dev/null +++ b/libsrc/protoserver/ProtoConnectionWrapper.cpp @@ -0,0 +1,19 @@ +// protoserver includes +#include "protoserver/ProtoConnectionWrapper.h" + +ProtoConnectionWrapper::ProtoConnectionWrapper(const std::string & address, int priority, int duration_ms, bool skipProtoReply) : + _priority(priority), + _duration_ms(duration_ms), + _connection(address) +{ + _connection.setSkipReply(skipProtoReply); +} + +ProtoConnectionWrapper::~ProtoConnectionWrapper() +{ +} + +void ProtoConnectionWrapper::receiveImage(const Image & image) +{ + _connection.setImage(image, _priority, _duration_ms); +} diff --git a/libsrc/protoserver/message.proto b/libsrc/protoserver/message.proto index ebc7cba2..33c995b3 100644 --- a/libsrc/protoserver/message.proto +++ b/libsrc/protoserver/message.proto @@ -17,7 +17,7 @@ message HyperionRequest { message ColorRequest { extend HyperionRequest { - required ColorRequest colorRequest = 10; + optional ColorRequest colorRequest = 10; } // priority to use when setting the color @@ -32,7 +32,7 @@ message ColorRequest { message ImageRequest { extend HyperionRequest { - required ImageRequest imageRequest = 11; + optional ImageRequest imageRequest = 11; } // priority to use when setting the image @@ -53,7 +53,7 @@ message ImageRequest { message ClearRequest { extend HyperionRequest { - required ClearRequest clearRequest = 12; + optional ClearRequest clearRequest = 12; } // priority which need to be cleared diff --git a/libsrc/utils/CMakeLists.txt b/libsrc/utils/CMakeLists.txt index b1f0709e..5549ce74 100644 --- a/libsrc/utils/CMakeLists.txt +++ b/libsrc/utils/CMakeLists.txt @@ -13,6 +13,12 @@ add_library(hyperion-utils ${CURRENT_HEADER_DIR}/Image.h ${CURRENT_HEADER_DIR}/Sleep.h + ${CURRENT_HEADER_DIR}/PixelFormat.h + ${CURRENT_HEADER_DIR}/VideoMode.h + + ${CURRENT_HEADER_DIR}/ImageResampler.h + ${CURRENT_SOURCE_DIR}/ImageResampler.cpp + ${CURRENT_HEADER_DIR}/HsvTransform.h ${CURRENT_SOURCE_DIR}/HsvTransform.cpp ${CURRENT_HEADER_DIR}/RgbChannelTransform.h diff --git a/libsrc/utils/ImageResampler.cpp b/libsrc/utils/ImageResampler.cpp new file mode 100644 index 00000000..d66ca742 --- /dev/null +++ b/libsrc/utils/ImageResampler.cpp @@ -0,0 +1,133 @@ +#include "utils/ImageResampler.h" + +ImageResampler::ImageResampler() : + _horizontalDecimation(1), + _verticalDecimation(1), + _cropLeft(0), + _cropRight(0), + _cropTop(0), + _cropBottom(0), + _videoMode(VIDEO_2D) +{ + +} + +ImageResampler::~ImageResampler() +{ + +} + +void ImageResampler::setHorizontalPixelDecimation(int decimator) +{ + _horizontalDecimation = decimator; +} + +void ImageResampler::setVerticalPixelDecimation(int decimator) +{ + _verticalDecimation = decimator; +} + +void ImageResampler::setCropping(int cropLeft, int cropRight, int cropTop, int cropBottom) +{ + _cropLeft = cropLeft; + _cropRight = cropRight; + _cropTop = cropTop; + _cropBottom = cropBottom; +} + +void ImageResampler::set3D(VideoMode mode) +{ + _videoMode = mode; +} + +void ImageResampler::processImage(const uint8_t * data, int width, int height, int lineLength, PixelFormat pixelFormat, Image &outputImage) const +{ + int cropLeft = _cropLeft; + int cropRight = _cropRight; + int cropTop = _cropTop; + int cropBottom = _cropBottom; + + // handle 3D mode + switch (_videoMode) + { + case VIDEO_3DSBS: + cropRight = width/2; + break; + case VIDEO_3DTAB: + cropBottom = height/2; + break; + default: + break; + } + + // calculate the output size + int outputWidth = (width - cropLeft - cropRight - _horizontalDecimation/2 + _horizontalDecimation - 1) / _horizontalDecimation; + int outputHeight = (height - cropTop - cropBottom - _verticalDecimation/2 + _verticalDecimation - 1) / _verticalDecimation; + outputImage.resize(outputWidth, outputHeight); + + for (int yDest = 0, ySource = cropTop + _verticalDecimation/2; yDest < outputHeight; ySource += _verticalDecimation, ++yDest) + { + for (int xDest = 0, xSource = cropLeft + _horizontalDecimation/2; xDest < outputWidth; xSource += _horizontalDecimation, ++xDest) + { + ColorRgb & rgb = outputImage(xDest, yDest); + + switch (pixelFormat) + { + case PIXELFORMAT_UYVY: + { + int index = lineLength * ySource + xSource * 2; + uint8_t y = data[index+1]; + uint8_t u = ((xSource&1) == 0) ? data[index ] : data[index-2]; + uint8_t v = ((xSource&1) == 0) ? data[index+2] : data[index ]; + yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + } + break; + case PIXELFORMAT_YUYV: + { + int index = lineLength * ySource + xSource * 2; + uint8_t y = data[index]; + uint8_t u = ((xSource&1) == 0) ? data[index+1] : data[index-1]; + uint8_t v = ((xSource&1) == 0) ? data[index+3] : data[index+1]; + yuv2rgb(y, u, v, rgb.red, rgb.green, rgb.blue); + } + break; + case PIXELFORMAT_RGB32: + { + int index = lineLength * ySource + xSource * 4; + rgb.red = data[index ]; + rgb.green = data[index+1]; + rgb.blue = data[index+2]; + } + break; + case PIXELFORMAT_BGR32: + { + int index = lineLength * ySource + xSource * 4; + rgb.blue = data[index ]; + rgb.green = data[index+1]; + rgb.red = data[index+2]; + } + break; + case PIXELFORMAT_NO_CHANGE: + std::cerr << "Invalid pixel format given" << std::endl; + break; + } + } + } +} + +uint8_t ImageResampler::clamp(int x) +{ + return (x<0) ? 0 : ((x>255) ? 255 : uint8_t(x)); +} + +void ImageResampler::yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t &r, uint8_t &g, uint8_t &b) +{ + // see: http://en.wikipedia.org/wiki/YUV#Y.27UV444_to_RGB888_conversion + int c = y - 16; + int d = u - 128; + int e = v - 128; + + r = clamp((298 * c + 409 * e + 128) >> 8); + g = clamp((298 * c - 100 * d - 208 * e + 128) >> 8); + b = clamp((298 * c + 516 * d + 128) >> 8); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b06b4ee7..098a08d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,15 @@ add_subdirectory(hyperiond) add_subdirectory(hyperion-remote) -if (ENABLE_V4L2) - add_subdirectory(hyperion-v4l2) -endif (ENABLE_V4L2) + +# The following clients depend on the protobuf library +if(ENABLE_PROTOBUF) + # Add the 'Video 4 Linux' grabber if it is enabled + if(ENABLE_V4L2) + add_subdirectory(hyperion-v4l2) + endif() + + # Add the X11 grabber if it is enabled + if(ENABLE_X11) + add_subdirectory(hyperion-x11) + endif() +endif() diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index 676dc7d3..c934be21 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 2.8) project(hyperion-remote) -# add protocol buffers -find_package(Protobuf REQUIRED) - # find Qt4 find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 7d13baf8..6607c04f 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -2,44 +2,29 @@ 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} + ${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver ${PROTOBUF_INCLUDE_DIRS} ${QT_INCLUDES} ) set(Hyperion_V4L2_QT_HEADERS - ImageHandler.h ScreenshotHandler.h ) set(Hyperion_V4L2_HEADERS VideoStandardParameter.h PixelFormatParameter.h - ProtoConnection.h ) set(Hyperion_V4L2_SOURCES hyperion-v4l2.cpp - ProtoConnection.cpp - ImageHandler.cpp ScreenshotHandler.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} -) - QT4_WRAP_CPP(Hyperion_V4L2_MOC_SOURCES ${Hyperion_V4L2_QT_HEADERS}) add_executable(hyperion-v4l2 @@ -47,8 +32,6 @@ add_executable(hyperion-v4l2 ${Hyperion_V4L2_SOURCES} ${Hyperion_V4L2_QT_HEADERS} ${Hyperion_V4L2_MOC_SOURCES} - ${Hyperion_V4L2_PROTO_SRCS} - ${Hyperion_V4L2_PROTO_HDRS} ) target_link_libraries(hyperion-v4l2 @@ -56,7 +39,7 @@ target_link_libraries(hyperion-v4l2 getoptPlusPlus blackborder hyperion-utils - ${PROTOBUF_LIBRARIES} + protoserver pthread ${QT_LIBRARIES} ) diff --git a/src/hyperion-v4l2/ImageHandler.cpp b/src/hyperion-v4l2/ImageHandler.cpp deleted file mode 100644 index 19a1da80..00000000 --- a/src/hyperion-v4l2/ImageHandler.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// hyperion-v4l2 includes -#include "ImageHandler.h" - -ImageHandler::ImageHandler(const std::string & address, int priority, bool skipProtoReply) : - _priority(priority), - _connection(address) -{ - _connection.setSkipReply(skipProtoReply); -} - -ImageHandler::~ImageHandler() -{ -} - -void ImageHandler::receiveImage(const Image & image) -{ - _connection.setImage(image, _priority, 1000); -} diff --git a/src/hyperion-v4l2/ImageHandler.h b/src/hyperion-v4l2/ImageHandler.h deleted file mode 100644 index 8730989d..00000000 --- a/src/hyperion-v4l2/ImageHandler.h +++ /dev/null @@ -1,31 +0,0 @@ -// Qt includes -#include - -// hyperion includes -#include -#include - -// hyperion v4l2 includes -#include "ProtoConnection.h" - -/// This class handles callbacks from the V4L2 grabber -class ImageHandler : public QObject -{ - Q_OBJECT - -public: - ImageHandler(const std::string & address, int priority, bool skipProtoReply); - virtual ~ImageHandler(); - -public slots: - /// Handle a single image - /// @param image The image to process - void receiveImage(const Image & image); - -private: - /// Priority for calls to Hyperion - const int _priority; - - /// Hyperion proto connection object - ProtoConnection _connection; -}; diff --git a/src/hyperion-v4l2/PixelFormatParameter.h b/src/hyperion-v4l2/PixelFormatParameter.h index f9dff93e..3ebccfc7 100644 --- a/src/hyperion-v4l2/PixelFormatParameter.h +++ b/src/hyperion-v4l2/PixelFormatParameter.h @@ -2,7 +2,7 @@ #include // grabber includes -#include +#include using namespace vlofgren; @@ -10,34 +10,34 @@ using namespace vlofgren; typedef vlofgren::PODParameter PixelFormatParameter; namespace vlofgren { - /// Translates a string (as passed on the commandline) to a pixel format - /// - /// @param[in] s The string (as passed on the commandline) - /// @return The pixel format - /// @throws Parameter::ParameterRejected If the string did not result in a pixel format - template<> - PixelFormat PixelFormatParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) - { - QString input = QString::fromStdString(s).toLower(); + /// Translates a string (as passed on the commandline) to a pixel format + /// + /// @param[in] s The string (as passed on the commandline) + /// @return The pixel format + /// @throws Parameter::ParameterRejected If the string did not result in a pixel format + template<> + PixelFormat PixelFormatParameter::validate(const std::string& s) throw (Parameter::ParameterRejected) + { + QString input = QString::fromStdString(s).toLower(); - if (input == "yuyv") - { - return PIXELFORMAT_YUYV; - } - else if (input == "uyvy") - { - return PIXELFORMAT_UYVY; - } - else if (input == "rgb32") - { - return PIXELFORMAT_RGB32; - } - else if (input == "no-change") - { - return PIXELFORMAT_NO_CHANGE; - } + if (input == "yuyv") + { + return PIXELFORMAT_YUYV; + } + else if (input == "uyvy") + { + return PIXELFORMAT_UYVY; + } + else if (input == "rgb32") + { + return PIXELFORMAT_RGB32; + } + else if (input == "no-change") + { + return PIXELFORMAT_NO_CHANGE; + } - throw Parameter::ParameterRejected("Invalid value for pixel format. Valid values are: YUYV, UYVY, RGB32, and NO-CHANGE"); - return PIXELFORMAT_NO_CHANGE; - } + throw Parameter::ParameterRejected("Invalid value for pixel format. Valid values are: YUYV, UYVY, RGB32, and NO-CHANGE"); + return PIXELFORMAT_NO_CHANGE; + } } diff --git a/src/hyperion-v4l2/ProtoConnection.cpp b/src/hyperion-v4l2/ProtoConnection.cpp deleted file mode 100644 index 9f59eddd..00000000 --- a/src/hyperion-v4l2/ProtoConnection.cpp +++ /dev/null @@ -1,188 +0,0 @@ -// stl includes -#include - -// Qt includes -#include - -// hyperion-v4l2 includes -#include "ProtoConnection.h" - -ProtoConnection::ProtoConnection(const std::string & a) : - _socket(), - _skipReply(false) -{ - 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()); - } - _host = parts[0]; - - bool ok; - _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()); - } - - // try to connect to host - std::cout << "Connecting to Hyperion: " << _host.toStdString() << ":" << _port << std::endl; - connectToHost(); -} - -ProtoConnection::~ProtoConnection() -{ - _socket.close(); -} - -void ProtoConnection::setSkipReply(bool skip) -{ - _skipReply = skip; -} - -void ProtoConnection::setColor(const ColorRgb & color, int priority, int duration) -{ - proto::HyperionRequest request; - request.set_command(proto::HyperionRequest::COLOR); - proto::ColorRequest * colorRequest = request.MutableExtension(proto::ColorRequest::colorRequest); - colorRequest->set_rgbcolor((color.red << 16) | (color.green << 8) | color.blue); - colorRequest->set_priority(priority); - colorRequest->set_duration(duration); - - // send command message - sendMessage(request); -} - -void ProtoConnection::setImage(const Image &image, int priority, int duration) -{ - proto::HyperionRequest request; - request.set_command(proto::HyperionRequest::IMAGE); - proto::ImageRequest * imageRequest = request.MutableExtension(proto::ImageRequest::imageRequest); - imageRequest->set_imagedata(image.memptr(), image.width() * image.height() * 3); - imageRequest->set_imagewidth(image.width()); - imageRequest->set_imageheight(image.height()); - imageRequest->set_priority(priority); - imageRequest->set_duration(duration); - - // send command message - sendMessage(request); -} - -void ProtoConnection::clear(int priority) -{ - proto::HyperionRequest request; - request.set_command(proto::HyperionRequest::CLEAR); - proto::ClearRequest * clearRequest = request.MutableExtension(proto::ClearRequest::clearRequest); - clearRequest->set_priority(priority); - - // send command message - sendMessage(request); -} - -void ProtoConnection::clearAll() -{ - proto::HyperionRequest request; - request.set_command(proto::HyperionRequest::CLEARALL); - - // send command message - sendMessage(request); -} - -void ProtoConnection::connectToHost() -{ - _socket.connectToHost(_host, _port); - if (_socket.waitForConnected()) { - std::cout << "Connected to Hyperion host" << std::endl; - } -} - -void ProtoConnection::sendMessage(const proto::HyperionRequest &message) -{ - if (_socket.state() == QAbstractSocket::UnconnectedState) - { - std::cout << "Currently disconnected: trying to connect to host" << std::endl; - connectToHost(); - } - - if (_socket.state() != QAbstractSocket::ConnectedState) - { - return; - } - - // We only get here if we are connected - - // serialize message (FastWriter already appends a newline) - std::string serializedMessage = message.SerializeAsString(); - - int length = serializedMessage.size(); - const uint8_t header[] = { - uint8_t((length >> 24) & 0xFF), - uint8_t((length >> 16) & 0xFF), - uint8_t((length >> 8) & 0xFF), - uint8_t((length ) & 0xFF)}; - - // write message - int count = 0; - count += _socket.write(reinterpret_cast(header), 4); - count += _socket.write(reinterpret_cast(serializedMessage.data()), length); - if (!_socket.waitForBytesWritten()) - { - std::cerr << "Error while writing data to host" << std::endl; - return; - } - - if (!_skipReply) - { - // read reply data - QByteArray serializedReply; - length = -1; - while (length < 0 && serializedReply.size() < length+4) - { - // receive reply - if (!_socket.waitForReadyRead()) - { - std::cerr << "Error while reading data from host" << std::endl; - return; - } - - serializedReply += _socket.readAll(); - - if (length < 0 && serializedReply.size() >= 4) - { - // read the message size - length = - ((serializedReply[0]<<24) & 0xFF000000) | - ((serializedReply[1]<<16) & 0x00FF0000) | - ((serializedReply[2]<< 8) & 0x0000FF00) | - ((serializedReply[3] ) & 0x000000FF); - } - } - - // parse reply data - proto::HyperionReply reply; - reply.ParseFromArray(serializedReply.constData()+4, length); - - // parse reply message - parseReply(reply); - } -} - -bool ProtoConnection::parseReply(const proto::HyperionReply &reply) -{ - bool success = false; - - if (!reply.success()) - { - if (reply.has_error()) - { - throw std::runtime_error("Error: " + reply.error()); - } - else - { - throw std::runtime_error("Error: No error info"); - } - } - - return success; -} diff --git a/src/hyperion-v4l2/ProtoConnection.h b/src/hyperion-v4l2/ProtoConnection.h deleted file mode 100644 index a220c794..00000000 --- a/src/hyperion-v4l2/ProtoConnection.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -// stl includes -#include - -// Qt includes -#include -#include -#include -#include - -// hyperion util -#include -#include - -// jsoncpp includes -#include - -/// -/// 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(); - - /// Do not read reply messages from Hyperion if set to true - void setSkipReply(bool skip); - - /// - /// Set all leds to the specified color - /// - /// @param color The color - /// @param priority The priority - /// @param duration The duration in milliseconds - /// - void setColor(const ColorRgb & color, int priority, int duration = 1); - - /// - /// 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 & image, int priority, int duration = -1); - - /// - /// Clear the given priority channel - /// - /// @param priority The priority - /// - void clear(int priority); - - /// - /// Clear all priority channels - /// - void clearAll(); - -private: - /// Try to connect to the Hyperion host - void connectToHost(); - - /// - /// Send a command message and receive its reply - /// - /// @param message The message to send - /// - void sendMessage(const proto::HyperionRequest & message); - - /// - /// Parse a reply message - /// - /// @param reply The received reply - /// - /// @return true if the reply indicates success - /// - bool parseReply(const proto::HyperionReply & reply); - -private: - /// The TCP-Socket with the connection to the server - QTcpSocket _socket; - - /// Host address - QString _host; - - /// Host port - uint16_t _port; - - /// Skip receiving reply messages from Hyperion if set - bool _skipReply; -}; diff --git a/src/hyperion-v4l2/hyperion-v4l2.cpp b/src/hyperion-v4l2/hyperion-v4l2.cpp index 86c23781..fe011f6d 100644 --- a/src/hyperion-v4l2/hyperion-v4l2.cpp +++ b/src/hyperion-v4l2/hyperion-v4l2.cpp @@ -15,11 +15,13 @@ // grabber includes #include "grabber/V4L2Grabber.h" +// proto includes +#include "protoserver/ProtoConnection.h" +#include "protoserver/ProtoConnectionWrapper.h" + // hyperion-v4l2 includes -#include "ProtoConnection.h" #include "VideoStandardParameter.h" #include "PixelFormatParameter.h" -#include "ImageHandler.h" #include "ScreenshotHandler.h" using namespace vlofgren; @@ -27,144 +29,145 @@ using namespace vlofgren; // save the image as screenshot void saveScreenshot(void *, const Image & 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"); + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save("screenshot.png"); } int main(int argc, char** argv) { - QCoreApplication app(argc, argv); + QCoreApplication app(argc, argv); - // force the locale - setlocale(LC_ALL, "C"); - QLocale::setDefault(QLocale::c()); + // force the locale + setlocale(LC_ALL, "C"); + QLocale::setDefault(QLocale::c()); - // register the image type to use in signals - qRegisterMetaType>("Image"); + // register the image type to use in signals + qRegisterMetaType>("Image"); - try - { - // create the option parser and initialize all parameters - OptionsParser optionParser("V4L capture application for Hyperion"); - ParameterSet & parameters = optionParser.getParameters(); + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("V4L capture application for Hyperion"); + ParameterSet & parameters = optionParser.getParameters(); - StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); - VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL or NTSC (optional)"); - PixelFormatParameter & argPixelFormat = parameters.add (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, and RGB32 (optional)"); - IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); - IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); - IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); - IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default=0]"); - IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default=0]"); - IntParameter & argCropLeft = parameters.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); - IntParameter & argCropRight = parameters.add (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)"); - IntParameter & argCropTop = parameters.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); - IntParameter & argCropBottom = parameters.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); - IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); - IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator", "Decimation factor for the video frames [default=1]"); - SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); - DoubleParameter & argSignalThreshold = parameters.add ('t', "signal-threshold", "The signal threshold for detecting the presence of a signal. Value should be between 0.0 and 1.0."); - DoubleParameter & argRedSignalThreshold = parameters.add (0x0, "red-threshold", "The red signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); - DoubleParameter & argGreenSignalThreshold = parameters.add (0x0, "green-threshold", "The green signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); - DoubleParameter & argBlueSignalThreshold = parameters.add (0x0, "blue-threshold", "The blue signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); - SwitchParameter<> & arg3DSBS = parameters.add> (0x0, "3DSBS", "Interpret the incoming video stream as 3D side-by-side"); - SwitchParameter<> & arg3DTAB = parameters.add> (0x0, "3DTAB", "Interpret the incoming video stream as 3D top-and-bottom"); - StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); - IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); - SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); - SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + StringParameter & argDevice = parameters.add ('d', "device", "The device to use [default=/dev/video0]"); + VideoStandardParameter & argVideoStandard = parameters.add('v', "video-standard", "The used video standard. Valid values are PAL or NTSC (optional)"); + PixelFormatParameter & argPixelFormat = parameters.add (0x0, "pixel-format", "The use pixel format. Valid values are YUYV, UYVY, and RGB32 (optional)"); + IntParameter & argInput = parameters.add (0x0, "input", "Input channel (optional)"); + IntParameter & argWidth = parameters.add (0x0, "width", "Try to set the width of the video input (optional)"); + IntParameter & argHeight = parameters.add (0x0, "height", "Try to set the height of the video input (optional)"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default=0]"); + IntParameter & argCropLeft = parameters.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropRight = parameters.add (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropTop = parameters.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); + IntParameter & argCropBottom = parameters.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=1]"); + IntParameter & argFrameDecimation = parameters.add ('f', "frame-decimator", "Decimation factor for the video frames [default=1]"); + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + DoubleParameter & argSignalThreshold = parameters.add ('t', "signal-threshold", "The signal threshold for detecting the presence of a signal. Value should be between 0.0 and 1.0."); + DoubleParameter & argRedSignalThreshold = parameters.add (0x0, "red-threshold", "The red signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + DoubleParameter & argGreenSignalThreshold = parameters.add (0x0, "green-threshold", "The green signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + DoubleParameter & argBlueSignalThreshold = parameters.add (0x0, "blue-threshold", "The blue signal threshold. Value should be between 0.0 and 1.0. (overrides --signal-threshold)"); + SwitchParameter<> & arg3DSBS = parameters.add> (0x0, "3DSBS", "Interpret the incoming video stream as 3D side-by-side"); + SwitchParameter<> & arg3DTAB = parameters.add> (0x0, "3DTAB", "Interpret the incoming video stream as 3D top-and-bottom"); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); - // set defaults - argDevice.setDefault("/dev/video0"); - argVideoStandard.setDefault(VIDEOSTANDARD_NO_CHANGE); - argPixelFormat.setDefault(PIXELFORMAT_NO_CHANGE); - 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); - argSignalThreshold.setDefault(-1); + // set defaults + argDevice.setDefault("/dev/video0"); + argVideoStandard.setDefault(VIDEOSTANDARD_NO_CHANGE); + argPixelFormat.setDefault(PIXELFORMAT_NO_CHANGE); + 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); + argSignalThreshold.setDefault(-1); - // parse all options - optionParser.parse(argc, const_cast(argv)); + // parse all options + optionParser.parse(argc, const_cast(argv)); - // check if we need to display the usage. exit if we do. - if (argHelp.isSet()) - { - optionParser.usage(); - return 0; - } + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } - if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue()); - if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue()); - if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue()); - if (!argCropBottom.isSet()) argCropBottom.setDefault(argCropHeight.getValue()); + // cropping values if not defined + if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue()); + if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue()); + if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue()); + if (!argCropBottom.isSet()) argCropBottom.setDefault(argCropHeight.getValue()); - // initialize the grabber - V4L2Grabber grabber( - argDevice.getValue(), - argInput.getValue(), - argVideoStandard.getValue(), - argPixelFormat.getValue(), - argWidth.getValue(), - argHeight.getValue(), - std::max(1, argFrameDecimation.getValue()), - std::max(1, argSizeDecimation.getValue()), - std::max(1, argSizeDecimation.getValue())); + // initialize the grabber + V4L2Grabber grabber( + argDevice.getValue(), + argInput.getValue(), + argVideoStandard.getValue(), + argPixelFormat.getValue(), + argWidth.getValue(), + argHeight.getValue(), + std::max(1, argFrameDecimation.getValue()), + std::max(1, argSizeDecimation.getValue()), + std::max(1, argSizeDecimation.getValue())); - // set signal detection - grabber.setSignalThreshold( - std::min(1.0, std::max(0.0, argRedSignalThreshold.isSet() ? argRedSignalThreshold.getValue() : argSignalThreshold.getValue())), - std::min(1.0, std::max(0.0, argGreenSignalThreshold.isSet() ? argGreenSignalThreshold.getValue() : argSignalThreshold.getValue())), - std::min(1.0, std::max(0.0, argBlueSignalThreshold.isSet() ? argBlueSignalThreshold.getValue() : argSignalThreshold.getValue())), - 50); + // set signal detection + grabber.setSignalThreshold( + std::min(1.0, std::max(0.0, argRedSignalThreshold.isSet() ? argRedSignalThreshold.getValue() : argSignalThreshold.getValue())), + std::min(1.0, std::max(0.0, argGreenSignalThreshold.isSet() ? argGreenSignalThreshold.getValue() : argSignalThreshold.getValue())), + std::min(1.0, std::max(0.0, argBlueSignalThreshold.isSet() ? argBlueSignalThreshold.getValue() : argSignalThreshold.getValue())), + 50); - // set cropping values - grabber.setCropping( - std::max(0, argCropLeft.getValue()), - std::max(0, argCropRight.getValue()), - std::max(0, argCropTop.getValue()), - std::max(0, argCropBottom.getValue())); + // set cropping values + grabber.setCropping( + std::max(0, argCropLeft.getValue()), + std::max(0, argCropRight.getValue()), + std::max(0, argCropTop.getValue()), + std::max(0, argCropBottom.getValue())); - // set 3D mode if applicable - if (arg3DSBS.isSet()) - { - grabber.set3D(VIDEO_3DSBS); - } - else if (arg3DTAB.isSet()) - { - grabber.set3D(VIDEO_3DTAB); - } + // set 3D mode if applicable + if (arg3DSBS.isSet()) + { + grabber.set3D(VIDEO_3DSBS); + } + else if (arg3DTAB.isSet()) + { + grabber.set3D(VIDEO_3DTAB); + } - // run the grabber - if (argScreenshot.isSet()) - { - ScreenshotHandler handler("screenshot.png"); - QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); - grabber.start(); - QCoreApplication::exec(); - grabber.stop(); - } - else - { - ImageHandler handler(argAddress.getValue(), argPriority.getValue(), argSkipReply.isSet()); - QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); - grabber.start(); - QCoreApplication::exec(); - grabber.stop(); - } - } - catch (const std::runtime_error & e) - { - // An error occured. Display error and quit - std::cerr << e.what() << std::endl; - return 1; - } + // run the grabber + if (argScreenshot.isSet()) + { + ScreenshotHandler handler("screenshot.png"); + QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); + grabber.start(); + QCoreApplication::exec(); + grabber.stop(); + } + else + { + ProtoConnectionWrapper handler(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet()); + QObject::connect(&grabber, SIGNAL(newFrame(Image)), &handler, SLOT(receiveImage(Image))); + grabber.start(); + QCoreApplication::exec(); + 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; + return 0; } diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt new file mode 100644 index 00000000..89912f66 --- /dev/null +++ b/src/hyperion-x11/CMakeLists.txt @@ -0,0 +1,52 @@ +# Configure minimum CMAKE version +cmake_minimum_required(VERSION 2.8) + +# Set the project name +project(hyperion-x11) + +# find Qt4 +find_package(Qt4 REQUIRED QtCore QtGui QtNetwork) + +# Find X11 +find_package(X11 REQUIRED) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/protoserver + ${QT_INCLUDES} + ${X11_INCLUDES} + ${PROTOBUF_INCLUDE_DIRS} +) + +set(Hyperion_X11_QT_HEADERS + X11Wrapper.h) + +set(Hyperion_X11_HEADERS +) + +set(Hyperion_X11_SOURCES + hyperion-x11.cpp + X11Wrapper.cpp +) + +QT4_WRAP_CPP(Hyperion_X11_HEADERS_MOC ${Hyperion_X11_QT_HEADERS}) + +add_executable(hyperion-x11 + ${Hyperion_X11_HEADERS} + ${Hyperion_X11_SOURCES} + ${Hyperion_X11_HEADERS_MOC} +) + +target_link_libraries(hyperion-x11 + getoptPlusPlus + blackborder + hyperion-utils + protoserver + x11-grabber + ${X11_LIBRARIES} + pthread +) + +qt4_use_modules(hyperion-x11 + Core + Gui + Network) diff --git a/src/hyperion-x11/X11Wrapper.cpp b/src/hyperion-x11/X11Wrapper.cpp new file mode 100644 index 00000000..6f33cc61 --- /dev/null +++ b/src/hyperion-x11/X11Wrapper.cpp @@ -0,0 +1,36 @@ + +// Hyperion-X11 includes +#include "X11Wrapper.h" + +X11Wrapper::X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation) : + _timer(this), + _grabber(cropLeft, cropRight, cropTop, cropBottom, horizontalPixelDecimation, verticalPixelDecimation) +{ + _timer.setSingleShot(false); + _timer.setInterval(grabInterval); + + // Connect capturing to the timeout signal of the timer + connect(&_timer, SIGNAL(timeout()), this, SLOT(capture())); +} + +const Image & X11Wrapper::getScreenshot() +{ + const Image & screenshot = _grabber.grab(); + return screenshot; +} + +void X11Wrapper::start() +{ + _timer.start(); +} + +void X11Wrapper::stop() +{ + _timer.stop(); +} + +void X11Wrapper::capture() +{ + const Image & screenshot = _grabber.grab(); + emit sig_screenshot(screenshot); +} diff --git a/src/hyperion-x11/X11Wrapper.h b/src/hyperion-x11/X11Wrapper.h new file mode 100644 index 00000000..211dfe18 --- /dev/null +++ b/src/hyperion-x11/X11Wrapper.h @@ -0,0 +1,39 @@ + +// QT includes +#include + +// Hyperion-X11 includes +#include + +class X11Wrapper : public QObject +{ + Q_OBJECT +public: + X11Wrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int horizontalPixelDecimation, int verticalPixelDecimation); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + +signals: + void sig_screenshot(const Image & screenshot); + +private slots: + /// + /// Performs a single screenshot capture and publishes the capture screenshot on the screenshot + /// signal. + /// + void capture(); + +private: + /// The QT timer to generate capture-publish events + QTimer _timer; + + /// The grabber for creating screenshots + X11Grabber _grabber; +}; diff --git a/src/hyperion-x11/hyperion-x11.cpp b/src/hyperion-x11/hyperion-x11.cpp new file mode 100644 index 00000000..449f3990 --- /dev/null +++ b/src/hyperion-x11/hyperion-x11.cpp @@ -0,0 +1,110 @@ + +// QT includes +#include +#include + +// getoptPlusPLus includes +#include + +#include "protoserver/ProtoConnectionWrapper.h" +#include "X11Wrapper.h" + +using namespace vlofgren; + +// save the image as screenshot +void saveScreenshot(const char * filename, const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save(filename); +} + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + + try + { + // create the option parser and initialize all parameters + OptionsParser optionParser("X11 capture application for Hyperion"); + ParameterSet & parameters = optionParser.getParameters(); + + IntParameter & argFps = parameters.add ('f', "framerate", "Capture frame rate [default=10]"); + IntParameter & argCropWidth = parameters.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default=0]"); + IntParameter & argCropHeight = parameters.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default=0]"); + IntParameter & argCropLeft = parameters.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropRight = parameters.add (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)"); + IntParameter & argCropTop = parameters.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); + IntParameter & argCropBottom = parameters.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); + IntParameter & argSizeDecimation = parameters.add ('s', "size-decimator", "Decimation factor for the output size [default=8]"); + SwitchParameter<> & argScreenshot = parameters.add> (0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + StringParameter & argAddress = parameters.add ('a', "address", "Set the address of the hyperion server [default: 127.0.0.1:19445]"); + IntParameter & argPriority = parameters.add ('p', "priority", "Use the provided priority channel (the lower the number, the higher the priority) [default: 800]"); + SwitchParameter<> & argSkipReply = parameters.add> (0x0, "skip-reply", "Do not receive and check reply messages from Hyperion"); + SwitchParameter<> & argHelp = parameters.add> ('h', "help", "Show this help message and exit"); + + // set defaults + argFps.setDefault(10); + argCropWidth.setDefault(0); + argCropHeight.setDefault(0); + argSizeDecimation.setDefault(8); + argAddress.setDefault("127.0.0.1:19445"); + argPriority.setDefault(800); + + // parse all options + optionParser.parse(argc, const_cast(argv)); + + // check if we need to display the usage. exit if we do. + if (argHelp.isSet()) + { + optionParser.usage(); + return 0; + } + + // cropping values if not defined + if (!argCropLeft.isSet()) argCropLeft.setDefault(argCropWidth.getValue()); + if (!argCropRight.isSet()) argCropRight.setDefault(argCropWidth.getValue()); + if (!argCropTop.isSet()) argCropTop.setDefault(argCropHeight.getValue()); + if (!argCropBottom.isSet()) argCropBottom.setDefault(argCropHeight.getValue()); + + // Create the X11 grabbing stuff + int grabInterval = 1000 / argFps.getValue(); + X11Wrapper x11Wrapper( + grabInterval, + argCropLeft.getValue(), + argCropRight.getValue(), + argCropTop.getValue(), + argCropBottom.getValue(), + argSizeDecimation.getValue(), // horizontal decimation + argSizeDecimation.getValue()); // vertical decimation + + if (argScreenshot.isSet()) + { + // Capture a single screenshot and finish + const Image & screenshot = x11Wrapper.getScreenshot(); + saveScreenshot("screenshot.png", screenshot); + } + else + { + // Create the Proto-connection with hyperiond + ProtoConnectionWrapper protoWrapper(argAddress.getValue(), argPriority.getValue(), 1000, argSkipReply.isSet()); + + // Connect the screen capturing to the proto processing + QObject::connect(&x11Wrapper, SIGNAL(sig_screenshot(const Image &)), &protoWrapper, SLOT(receiveImage(Image))); + + // Start the capturing + x11Wrapper.start(); + + // Start the application + app.exec(); + } + } + catch (const std::runtime_error & e) + { + // An error occured. Display error and quit + std::cerr << e.what() << std::endl; + return -1; + } + + return 0; +} diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index f843b15f..49df4bfe 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -1,20 +1,23 @@ - -add_executable(hyperiond - hyperiond.cpp) - -target_link_libraries(hyperiond - hyperion - xbmcvideochecker - effectengine - jsonserver - protoserver - boblightserver -) - -if (ENABLE_DISPMANX) - target_link_libraries(hyperiond dispmanx-grabber) -endif (ENABLE_DISPMANX) - -if (ENABLE_V4L2) - target_link_libraries(hyperiond v4l2-grabber) -endif (ENABLE_V4L2) + +add_executable(hyperiond + hyperiond.cpp) + +target_link_libraries(hyperiond + hyperion + xbmcvideochecker + effectengine + jsonserver + boblightserver +) + +if (ENABLE_DISPMANX) + target_link_libraries(hyperiond dispmanx-grabber) +endif (ENABLE_DISPMANX) + +if (ENABLE_V4L2) + target_link_libraries(hyperiond v4l2-grabber) +endif (ENABLE_V4L2) + +if (ENABLE_PROTOBUF) + target_link_libraries(hyperiond protoserver) +endif (ENABLE_PROTOBUF) diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index b6365019..4c21b39b 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -36,8 +36,10 @@ // JsonServer includes #include +#ifdef ENABLE_PROTOBUF // ProtoServer includes #include +#endif // BoblightServer includes #include @@ -113,22 +115,22 @@ int main(int argc, char** argv) const unsigned duration_ms = effectConfig["duration_ms"].asUInt(); const int priority = 0; + hyperion.setColor(priority+1, ColorRgb::BLACK, duration_ms, false); + if (effectConfig.isMember("args")) { const Json::Value effectConfigArgs = effectConfig["args"]; if (hyperion.setEffect(effectName, effectConfigArgs, priority, duration_ms) == 0) - { - std::cout << "Boot sequence(" << effectName << ") with user-defined arguments created and started" << std::endl; - } - else - { - std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl; - } - + { + std::cout << "Boot sequence(" << effectName << ") with user-defined arguments created and started" << std::endl; + } + else + { + std::cout << "Failed to start boot sequence: " << effectName << " with user-defined arguments" << std::endl; + } } else { - if (hyperion.setEffect(effectName, priority, duration_ms) == 0) { std::cout << "Boot sequence(" << effectName << ") created and started" << std::endl; @@ -233,6 +235,7 @@ int main(int argc, char** argv) std::cout << "Json server created and started on port " << jsonServer->getPort() << std::endl; } +#ifdef ENABLE_PROTOBUF // Create Proto server if configuration is present ProtoServer * protoServer = nullptr; if (config.isMember("protoServer")) @@ -241,6 +244,7 @@ int main(int argc, char** argv) protoServer = new ProtoServer(&hyperion, protoServerConfig["port"].asUInt()); std::cout << "Proto server created and started on port " << protoServer->getPort() << std::endl; } +#endif // Create Boblight server if configuration is present BoblightServer * boblightServer = nullptr; @@ -264,7 +268,9 @@ int main(int argc, char** argv) #endif delete xbmcVideoChecker; delete jsonServer; +#ifdef ENABLE_PROTOBUF delete protoServer; +#endif delete boblightServer; // leave application diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa4b142b..bf40cebb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,3 +52,11 @@ target_link_libraries(test_qregexp add_executable(test_qtscreenshot TestQtScreenshot.cpp) target_link_libraries(test_qtscreenshot ${QT_LIBRARIES}) + +if(ENABLE_X11) + # Find X11 + find_package(X11 REQUIRED) + + add_executable(test_x11performance TestX11Performance.cpp) + target_link_libraries(test_x11performance ${X11_LIBRARIES} ${QT_LIBRARIES}) +endif(ENABLE_X11) diff --git a/test/TestQtScreenshot.cpp b/test/TestQtScreenshot.cpp index 27e03b0f..51500c77 100644 --- a/test/TestQtScreenshot.cpp +++ b/test/TestQtScreenshot.cpp @@ -6,15 +6,67 @@ #include #include #include +#include +#include + +#include + +// Utils includes +#include +#include + +void createScreenshot(const int cropHorizontal, const int cropVertical, const int decimation, Image & image) +{ + // Create the full size screenshot + const QRect screenSize = QApplication::desktop()->screenGeometry(); + const int croppedWidth = screenSize.width() - 2*cropVertical; + const int croppedHeight = screenSize.height() - 2*cropHorizontal; + const QPixmap fullSizeScreenshot = QPixmap::grabWindow(QApplication::desktop()->winId(), cropVertical, cropHorizontal, croppedWidth, croppedHeight); + + // Scale the screenshot to the required size + const int width = fullSizeScreenshot.width()/decimation; + const int height = fullSizeScreenshot.height()/decimation; + const QPixmap scaledScreenshot = fullSizeScreenshot.scaled(width, height, Qt::IgnoreAspectRatio, Qt::FastTransformation); + + // Convert the QPixmap to QImage in order to get out RGB values + const QImage qImage = scaledScreenshot.toImage(); + + // Make sure that the output image has the right size + image.resize(width, height); + + // Copy the data into the output image + for (int y=0; y> 16; + outPixel.green = (inPixel & 0x0000ff00) >> 8; + outPixel.blue = (inPixel & 0x000000ff); + } + } +} int main(int argc, char** argv) { + int decimation = 10; QApplication app(argc, argv); + QElapsedTimer timer; - QPixmap originalPixmap = QPixmap::grabWindow(QApplication::desktop()->winId()); + Image screenshot(64,64); - std::cout << "Grabbed image: [" << originalPixmap.width() << "; " << originalPixmap.height() << "]" << std::endl; + int loopCnt = 100; + timer.start(); + for (int i=0; i +#include + +#include + +#include +#include + +void foo_1(int pixelDecimation) +{ + int cropWidth = 0; + int cropHeight = 0; + + Image image(64, 64); + + /// Reference to the X11 display (nullptr if not opened) + Display * x11Display; + + const char * display_name = nullptr; + x11Display = XOpenDisplay(display_name); + + std::cout << "Opened display: " << x11Display << std::endl; + + XWindowAttributes window_attributes_return; + XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return); + + int screenWidth = window_attributes_return.width; + int screenHeight = window_attributes_return.height; + std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl; + + // Update the size of the buffer used to transfer the screenshot + int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation; + int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation; + image.resize(width, height); + + const int croppedWidth = screenWidth - 2*cropWidth; + const int croppedHeight = screenHeight - 2*cropHeight; + + QElapsedTimer timer; + timer.start(); + + XImage * xImage = XGetImage(x11Display, DefaultRootWindow(x11Display), cropWidth, cropHeight, croppedWidth, croppedHeight, AllPlanes, ZPixmap); + + std::cout << "Captured image: " << xImage << std::endl; + + // Copy the capture XImage to the local image (and apply required decimation) + ColorRgb * outputPtr = image.memptr(); + for (int iY=(pixelDecimation/2); iYred = uint8_t((pixel >> 16) & 0xff); + outputPtr->green = uint8_t((pixel >> 8) & 0xff); + outputPtr->blue = uint8_t((pixel >> 0) & 0xff); + + // Move to the next output pixel + ++outputPtr; + } + } + + // Cleanup allocated resources of the X11 grab + XDestroyImage(xImage); + + std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl; + + XCloseDisplay(x11Display); +} + +void foo_2(int pixelDecimation) +{ + int cropWidth = 0; + int cropHeight = 0; + + Image image(64, 64); + + /// Reference to the X11 display (nullptr if not opened) + Display * x11Display; + + const char * display_name = nullptr; + x11Display = XOpenDisplay(display_name); + + XWindowAttributes window_attributes_return; + XGetWindowAttributes(x11Display, DefaultRootWindow(x11Display), &window_attributes_return); + + int screenWidth = window_attributes_return.width; + int screenHeight = window_attributes_return.height; + std::cout << "[" << screenWidth << "x" << screenHeight <<"]" << std::endl; + + // Update the size of the buffer used to transfer the screenshot + int width = (screenWidth - 2 * cropWidth + pixelDecimation/2) / pixelDecimation; + int height = (screenHeight - 2 * cropHeight + pixelDecimation/2) / pixelDecimation; + image.resize(width, height); + + const int croppedWidth = screenWidth - 2*cropWidth; + const int croppedHeight = screenHeight - 2*cropHeight; + + QElapsedTimer timer; + timer.start(); + + // Copy the capture XImage to the local image (and apply required decimation) + ColorRgb * outputPtr = image.memptr(); + for (int iY=(pixelDecimation/2); iYred = uint8_t((pixel >> 16) & 0xff); + outputPtr->green = uint8_t((pixel >> 8) & 0xff); + outputPtr->blue = uint8_t((pixel >> 0) & 0xff); + + // Move to the next output pixel + ++outputPtr; + + // Cleanup allocated resources of the X11 grab + XDestroyImage(xImage); + } + } + std::cout << "Time required: " << timer.elapsed() << " ms" << std::endl; + + + XCloseDisplay(x11Display); +} + +int main() +{ + foo_1(10); + foo_2(10); + return 0; +}