diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a91bcf6d..01c3dab4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -264,7 +264,9 @@ if(USE_EPIPHANSDK) INCLUDE_DIRECTORIES(epiphansdk) LIST(APPEND HEADERS epiphansdk/epiphansdk_video_source.h) + LIST(APPEND HEADERS epiphansdk/rgb_to_bgra_converter.h) LIST(APPEND SOURCES epiphansdk/epiphansdk_video_source.cpp) + LIST(APPEND SOURCES epiphansdk/rgb_to_bgra_converter.cpp) endif(USE_EPIPHANSDK) # Blackmagic SDK video source diff --git a/src/api/videosourcefactory.cpp b/src/api/videosourcefactory.cpp index 5546a79a..6373e8cd 100644 --- a/src/api/videosourcefactory.cpp +++ b/src/api/videosourcefactory.cpp @@ -63,11 +63,12 @@ IVideoSource * VideoSourceFactory::get_device(Device device, // BGRA ======================================== case BGRA: -#ifdef USE_OPENCV - src = new VideoSourceOpenCV(0); +#ifdef USE_EPIPHANSDK + src = new VideoSourceEpiphanSDK(Epiphan_DVI2PCIeDuo_DVI, + V2U_GRABFRAME_FORMAT_RGB24); #else throw VideoSourceError( - "BGRA colour space on Epiphan DVI2PCIe Duo supported only with OpenCV"); + "BGRA colour space on Epiphan DVI2PCIe Duo supported only with Epiphan SDK"); #endif break; @@ -115,11 +116,12 @@ IVideoSource * VideoSourceFactory::get_device(Device device, // BGRA ======================================== case BGRA: -#ifdef USE_OPENCV - src = new VideoSourceOpenCV(1); +#ifdef USE_EPIPHANSDK + src = new VideoSourceEpiphanSDK(Epiphan_DVI2PCIeDuo_SDI, + V2U_GRABFRAME_FORMAT_RGB24); #else throw VideoSourceError( - "BGRA colour space on Epiphan DVI2PCIe Duo supported only with OpenCV"); + "BGRA colour space on Epiphan DVI2PCIe Duo supported only with Epiphan SDK"); #endif break; diff --git a/src/epiphansdk/epiphansdk_video_source.cpp b/src/epiphansdk/epiphansdk_video_source.cpp index fff34e90..628231c5 100644 --- a/src/epiphansdk/epiphansdk_video_source.cpp +++ b/src/epiphansdk/epiphansdk_video_source.cpp @@ -10,6 +10,8 @@ VideoSourceEpiphanSDK::VideoSourceEpiphanSDK( , _frame_grabber(nullptr) , _flags(0) , _daemon(nullptr) + , _bgra_data(nullptr) + , _buffer(nullptr) { FrmGrab_Init(); @@ -21,7 +23,7 @@ VideoSourceEpiphanSDK::VideoSourceEpiphanSDK( return; } - if (colour_space != V2U_GRABFRAME_FORMAT_I420 and colour_space != V2U_GRABFRAME_FORMAT_BGR24) + if (colour_space != V2U_GRABFRAME_FORMAT_I420 and colour_space != V2U_GRABFRAME_FORMAT_RGB24) { // TODO - exception GiftGrab#42 std::cerr << "Colour space " << colour_space << " not supported" << std::endl; @@ -30,7 +32,10 @@ VideoSourceEpiphanSDK::VideoSourceEpiphanSDK( else if (colour_space == V2U_GRABFRAME_FORMAT_I420) _colour = I420; else + { _colour = BGRA; + _flags |= V2U_GRABFRAME_BOTTOM_UP_FLAG; // for some reason without this the image is flipped + } _flags |= colour_space; VideoFrame frame(_colour); @@ -42,6 +47,12 @@ VideoSourceEpiphanSDK::VideoSourceEpiphanSDK( */ _full.width = 1920; _full.height = 1080; + if (_colour == BGRA) + { + _bgra_data = reinterpret_cast(malloc( + 4 * _full.width * _full.height * sizeof(unsigned char) + )); + } get_full_frame(); // TODO - exception GiftGrab#42 if (not get_frame(frame)) return; @@ -55,6 +66,11 @@ VideoSourceEpiphanSDK::~VideoSourceEpiphanSDK() delete _daemon; if (_frame_grabber) FrmGrab_Close(_frame_grabber); FrmGrab_Deinit(); + if (_colour == BGRA && _bgra_data != nullptr) + { + free(_bgra_data); + _bgra_data = nullptr; + } } bool VideoSourceEpiphanSDK::get_frame_dimensions(int & width, int & height) @@ -70,21 +86,42 @@ bool VideoSourceEpiphanSDK::get_frame(VideoFrame & frame) // TODO - exception GiftGrab#42 return false; + std::lock_guard buffer_lock_guard(_buffer_lock); + _buffer = FrmGrab_Frame(_frame_grabber, _flags, &_roi); if (_buffer) { + unsigned char *data = static_cast(_buffer->pixbuf); + size_t frame_data_length = _buffer->imagelen; + /* TODO #54 specified _roi not always + * respected by FrmGrab_Frame, hence + * constructing with _buffer->crop + * instead of _roi to avoid alignment + * problems when saving to video files + */ + size_t frame_width = _buffer->crop.width, + frame_height = _buffer->crop.height; + unsigned char *frame_data = nullptr; + switch(_colour) + { + case I420: + frame_data = data; + break; + case BGRA: + _rgb_to_bgra.set_frame_dimensions(frame_width, frame_height); + _rgb_to_bgra.convert(data, _bgra_data); + frame_data = _bgra_data; + break; + default: + // TODO + break; + } frame.init_from_specs( - static_cast(_buffer->pixbuf), - _buffer->imagelen, - /* TODO #54 specified _roi not always - * respected by FrmGrab_Frame, hence - * constructing with _buffer->crop - * instead of _roi to avoid alignment - * problems when saving to video files - */ - _buffer->crop.width, _buffer->crop.height - ); + frame_data, frame_data_length, + frame_width, frame_height + ); FrmGrab_Release(_frame_grabber, _buffer); + _buffer = nullptr; return true; } else @@ -93,6 +130,7 @@ bool VideoSourceEpiphanSDK::get_frame(VideoFrame & frame) double VideoSourceEpiphanSDK::get_frame_rate() { + double frame_rate = -1; if (_frame_grabber) { #if defined(Epiphan_DVI2PCIeDuo_DVI) and \ @@ -101,9 +139,18 @@ double VideoSourceEpiphanSDK::get_frame_rate() defined(Epiphan_DVI2PCIeDuo_SDI_MAX_FRAME_RATE) std::string port_id = FrmGrab_GetId(_frame_grabber); if (port_id == Epiphan_DVI2PCIeDuo_DVI) - return Epiphan_DVI2PCIeDuo_DVI_MAX_FRAME_RATE; + frame_rate = Epiphan_DVI2PCIeDuo_DVI_MAX_FRAME_RATE; else if (port_id == Epiphan_DVI2PCIeDuo_SDI) - return Epiphan_DVI2PCIeDuo_SDI_MAX_FRAME_RATE; + frame_rate = Epiphan_DVI2PCIeDuo_SDI_MAX_FRAME_RATE; + if (frame_rate > 0) + { + /* The above max frame rates are defined for I420, + * and are halved for BGRA, due to hardware bandwidth + */ + if (_colour == BGRA) + frame_rate /= 2.0; + return frame_rate; + } #endif } diff --git a/src/epiphansdk/epiphansdk_video_source.h b/src/epiphansdk/epiphansdk_video_source.h index bf0c7dd6..91719c89 100644 --- a/src/epiphansdk/epiphansdk_video_source.h +++ b/src/epiphansdk/epiphansdk_video_source.h @@ -3,6 +3,7 @@ #include "ivideosource.h" #include "macros.h" #include "broadcastdaemon.h" +#include "rgb_to_bgra_converter.h" namespace gg { @@ -37,11 +38,28 @@ class VideoSourceEpiphanSDK : public IVideoSource //! V2U_GrabFrame2 * _buffer; + //! + //! \brief This mutex to be locked when + //! accessing the buffer + //! \sa _buffer + //! + std::mutex _buffer_lock; + //! //! \brief //! gg::BroadcastDaemon * _daemon; + //! + //! \brief Buffer for converting from RGB to BGRA + //! + unsigned char *_bgra_data; + + //! + //! \brief RGB to BGRA converter + //! + RgbToBgraConverter _rgb_to_bgra; + public: //! //! \brief Connects to specified port of an Epiphan @@ -50,7 +68,7 @@ class VideoSourceEpiphanSDK : public IVideoSource //! as \c \#define'd in Epiphan device properties //! header //! \param colour_space \c V2U_GRABFRAME_FORMAT_I420 - //! or \c V2U_GRABFRAME_FORMAT_BGR24 + //! or \c V2U_GRABFRAME_FORMAT_RGB24 //! \throw VideoSourceError if connection attempt //! fails, with a detailed error message //! diff --git a/src/epiphansdk/rgb_to_bgra_converter.cpp b/src/epiphansdk/rgb_to_bgra_converter.cpp new file mode 100644 index 00000000..641e2b79 --- /dev/null +++ b/src/epiphansdk/rgb_to_bgra_converter.cpp @@ -0,0 +1,68 @@ +#include "rgb_to_bgra_converter.h" +#include + +namespace gg +{ + +RgbToBgraConverter::RgbToBgraConverter() + : _width(0) + , _height(0) +#ifdef USE_FFMPEG + , _sws_context(nullptr) +#endif +{ + +} + +RgbToBgraConverter::~RgbToBgraConverter() +{ +#ifdef USE_FFMPEG + sws_freeContext(_sws_context); + _sws_context = nullptr; +#endif +} + +void RgbToBgraConverter::convert(unsigned char *rgb, + unsigned char *bgra) +{ +#ifdef USE_FFMPEG + _sws_srcSlice[0] = rgb; + _sws_srcStride[0] = 3 * _width; + _sws_dst[0] = bgra; + _sws_dstStride[0] = 4 * _width; + sws_scale(_sws_context, + _sws_srcSlice, _sws_srcStride, 0, _height, + _sws_dst, _sws_dstStride); +#else + size_t length = 4 * _width * _height; + for (size_t i = 0, j = 0; i < length; i += 4, j += 3) + { + bgra[i] = rgb[j+2]; + bgra[i+1] = rgb[j+1]; + bgra[i+2] = rgb[j]; + bgra[i+3] = 255; + } +#endif +} + +void RgbToBgraConverter::set_frame_dimensions(size_t width, + size_t height) +{ + assert(width > 0); + assert(height > 0); + if (_width == width and _height == height) + return; + + _width = width; + _height = height; +#ifdef USE_FFMPEG + _sws_context = sws_getCachedContext( + _sws_context, + _width, _height, _sws_srcFormat, + _width, _height, _sws_dstFormat, + 0, nullptr, nullptr, nullptr // advanced + ); +#endif +} + +} diff --git a/src/epiphansdk/rgb_to_bgra_converter.h b/src/epiphansdk/rgb_to_bgra_converter.h new file mode 100644 index 00000000..5036dad7 --- /dev/null +++ b/src/epiphansdk/rgb_to_bgra_converter.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#ifdef USE_FFMPEG +extern "C" { +#include +} +#endif + +namespace gg +{ + +//! +//! \brief Epiphan SDK seems to support RGB but not BGRA. +//! This class abstracts the conversion of captured RGB +//! data to BGRA. +//! \sa VideoSourceEpiphanSDK +//! +class RgbToBgraConverter +{ +protected: + //! + //! \brief Width of frames passed for conversion + //! + size_t _width; + + //! + //! \brief Height of frames passed for conversion + //! + size_t _height; + +#ifdef USE_FFMPEG + unsigned char *_sws_srcSlice[1]; + int _sws_srcStride[1]; + unsigned char *_sws_dst[1]; + int _sws_dstStride[1]; + AVPixelFormat _sws_srcFormat = AV_PIX_FMT_RGB24; + AVPixelFormat _sws_dstFormat = AV_PIX_FMT_BGRA; + SwsContext *_sws_context; +#endif + +public: + //! + //! \brief Create a converter, which should be + //! initialised by informing it of the frame + //! dimensions + //! \sa set_frame_dimensions + //! + RgbToBgraConverter(); + + //! + //! \brief Free all allocated resources + //! + ~RgbToBgraConverter(); + +public: + //! + //! \brief Convert RGB data in passed buffer to BGRA + //! data, saving it into the passed output buffer. + //! \param rgb should be in line with the frame + //! dimensions set before + //! \param bgra should be at least as large as the + //! input buffer + //! \sa set_frame_dimensions + //! + void convert(unsigned char *rgb, unsigned char *bgra); + + //! + //! \brief Allocate all resources needed for RGB to + //! BGRA conversion based on passed frame dimensions + //! \param width + //! \param height + //! + void set_frame_dimensions(size_t width, size_t height); +}; + +} diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d44206cb..1b9138ce 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -74,3 +74,6 @@ endif(USE_FILES) # VideoSourceFactory leak checker ADD_SUBDIRECTORY(videosourcefactory) + +# Simple colour space conversion profiling +ADD_SUBDIRECTORY(rgbswap) diff --git a/src/tests/rgbswap/CMakeLists.txt b/src/tests/rgbswap/CMakeLists.txt new file mode 100644 index 00000000..0870d81c --- /dev/null +++ b/src/tests/rgbswap/CMakeLists.txt @@ -0,0 +1,14 @@ +SET(RGB_TO_BGRA_PROFILER rgb_to_bgra_profiler) +ADD_EXECUTABLE( + ${RGB_TO_BGRA_PROFILER} + ${CMAKE_SOURCE_DIR}/tests/rgbswap/profile_rgb_to_bgra.cpp +) +if(USE_FFMPEG) + LIST(APPEND RGB_TO_BGRA_PROFILER_LIBS ${FFmpeg_LIBS}) +endif(USE_FFMPEG) +if(USE_OPENCV) + LIST(APPEND RGB_TO_BGRA_PROFILER_LIBS ${OpenCV_LIBS}) +endif(USE_OPENCV) +TARGET_LINK_LIBRARIES( + ${RGB_TO_BGRA_PROFILER} ${RGB_TO_BGRA_PROFILER_LIBS} +) diff --git a/src/tests/rgbswap/profile_rgb_to_bgra.cpp b/src/tests/rgbswap/profile_rgb_to_bgra.cpp new file mode 100644 index 00000000..e6e866e8 --- /dev/null +++ b/src/tests/rgbswap/profile_rgb_to_bgra.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#ifdef USE_FFMPEG +extern "C" { +#include +} +#endif +#ifdef USE_OPENCV +#include +#endif + +using namespace std; +using namespace std::chrono; + +bool rgb_same_as_bgra(const unsigned char *rgb, + const unsigned char *bgra, + size_t l) +{ + for (size_t i = 0, j = 0; i < l; i += 4, j += 3) + if (rgb[j] != bgra[i+2] or rgb[j+1] != bgra[i+1] or + rgb[j+2] != bgra[i]) + return false; + return true; +} + +int main(int argc, char *argv[]) +{ + // RGB with random values + size_t w = 1920, h = 1080; + size_t l; + unsigned char *rgb = nullptr; + bool compose = true; + if (argc == 2) + { +#ifdef USE_OPENCV + cv::Mat orig = cv::imread(argv[1]); + cv::imwrite("bgr_orig.png", orig); + w = orig.cols; + h = orig.rows; + compose = false; + l = 3 * w * h; + rgb = reinterpret_cast(malloc(l * sizeof(unsigned char))); + for (size_t i = 0; i < l; i +=3) + { + rgb[i] = orig.data[i+2]; + rgb[i+1] = orig.data[i+1]; + rgb[i+2] = orig.data[i]; + } +#endif + } + else if (argc == 3) + { + w = atoi(argv[1]); + h = atoi(argv[2]); + } + cout << "Profiling RGB => BGRA conversion for " + << w << " x " << h << " image" << endl; + if (compose) + { + l = 3 * w * h; + rgb = reinterpret_cast(malloc(l * sizeof(unsigned char))); + for (size_t i = 0; i < l; i++) + { + rgb[i] = i % 32; + rgb[i+1] = rgb[i] * 4; + rgb[i+2] = i % 256; + } + } + + // RGB => BGRA in a strided loop + l = 4 * w * h; + unsigned char *bgra_loop = nullptr; + bgra_loop = reinterpret_cast(malloc(l * sizeof(unsigned char))); + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (size_t i = 0, j = 0; i < l; i += 4, j += 3) + { + bgra_loop[i] = rgb[j+2]; + bgra_loop[i+1] = rgb[j+1]; + bgra_loop[i+2] = rgb[j]; + bgra_loop[i+3] = 255; + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + auto duration = duration_cast( t2 - t1 ).count(); + cout << "Loop (" << (rgb_same_as_bgra(rgb, bgra_loop, l) ? "success" : "failure") + << ") took: " << duration << " usec" << endl; +#ifdef USE_OPENCV + { + cv::Mat _bgra(h, w, CV_8UC4, bgra_loop); + cv::imwrite("bgra_loop.png", _bgra); + } +#endif + free(bgra_loop); + + // simple memcopy (no RGB => BGRA) + l = 3 * w * h; + unsigned char *rgb_memcpy = nullptr; + rgb_memcpy = reinterpret_cast(malloc(l * sizeof(unsigned char))); + t1 = high_resolution_clock::now(); + memcpy(rgb_memcpy, rgb, l * sizeof(unsigned char)); + t2 = high_resolution_clock::now(); + duration = duration_cast( t2 - t1 ).count(); + cout << "memcpy took: " << duration << " usec" << endl; + free(rgb_memcpy); + +#ifdef USE_FFMPEG + l = 4 * w * h; + // RGB => BGRA using FFmpeg + unsigned char *bgra_ffmpeg = nullptr; + bgra_ffmpeg = reinterpret_cast(malloc(l * sizeof(unsigned char))); + unsigned char *srcSlice[] = {rgb}; + int srcStride[] = {static_cast(3 * w)}; + int srcSliceY = 0; + int srcSliceH = h; + unsigned char *dst[] = {bgra_ffmpeg}; + int dstStride[] = {static_cast(4 * w)}; + int srcW = w, srcH = h, dstW = w, dstH = h; + AVPixelFormat srcFormat = AV_PIX_FMT_RGB24, dstFormat = AV_PIX_FMT_BGRA; + SwsContext *c = sws_getCachedContext(nullptr, srcW, srcH, srcFormat, dstW, dstH, dstFormat, 0, nullptr, nullptr, nullptr); + t1 = high_resolution_clock::now(); + sws_scale(c, srcSlice, srcStride, srcSliceY, srcSliceH, dst, dstStride); + t2 = high_resolution_clock::now(); + duration = duration_cast( t2 - t1 ).count(); + cout << "FFmpeg (" << (rgb_same_as_bgra(rgb, bgra_ffmpeg, l) ? "success" : "failure") + << ") took: " << duration << " usec" << endl; +#ifdef USE_OPENCV + { + cv::Mat _bgra(h, w, CV_8UC4, bgra_ffmpeg), bgr; + cv::cvtColor(_bgra, bgr, cv::COLOR_BGRA2BGR); + cv::imwrite("bgra_ffmpeg.png", bgr); + } +#endif + free(bgra_ffmpeg); +#endif + + // free all memory + free(rgb); + return EXIT_SUCCESS; +}