// STL includes #include #include //Qt #include #include #include #include // Constants namespace { const bool verbose = false; const int DEFAULT_DEVICE = 0; } //End of constants // Local includes #include "grabber/DispmanxFrameGrabber.h" DispmanxFrameGrabber::DispmanxFrameGrabber() : Grabber("DISPMANXGRABBER") , _vc_display(0) , _vc_resource(0) , _vc_flags(DISPMANX_TRANSFORM_T(0)) , _captureBuffer(new ColorRgba[0]) , _captureBufferSize(0) , _image_rgba() { _useImageResampler = true; // Initialise BCM bcm_host_init(); } DispmanxFrameGrabber::~DispmanxFrameGrabber() { freeResources(); // De-init BCM bcm_host_deinit(); } bool DispmanxFrameGrabber::setupScreen() { bool rc (false); int deviceIdx (DEFAULT_DEVICE); QSize screenSize = getScreenSize(deviceIdx); if ( screenSize.isEmpty() ) { Error(_log, "Failed to open display [%d]! Probably no permissions to access the capture interface", deviceIdx); setEnabled(false); } else { setWidthHeight(screenSize.width(), screenSize.height()); Info(_log, "Display [%d] opened with resolution: %dx%d", deviceIdx, screenSize.width(), screenSize.height()); setEnabled(true); rc = true; } return rc; } void DispmanxFrameGrabber::freeResources() { delete[] _captureBuffer; // Clean up resources vc_dispmanx_resource_delete(_vc_resource); } bool DispmanxFrameGrabber::setWidthHeight(int width, int height) { bool rc = false; if(Grabber::setWidthHeight(width, height)) { if(_vc_resource != 0) { vc_dispmanx_resource_delete(_vc_resource); } Debug(_log,"Create the resources for capturing image"); uint32_t vc_nativeImageHandle; _vc_resource = vc_dispmanx_resource_create( VC_IMAGE_RGBA32, width, height, &vc_nativeImageHandle); assert(_vc_resource); if (_vc_resource != 0) { Debug(_log,"Define the capture rectangle with the same size"); vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height); rc = true; } } return rc; } void DispmanxFrameGrabber::setFlags(DISPMANX_TRANSFORM_T vc_flags) { _vc_flags = vc_flags; } int DispmanxFrameGrabber::grabFrame(Image & image) { int rc = 0; if (_isEnabled && !_isDeviceInError) { // vc_dispmanx_resource_read_data doesn't seem to work well // with arbitrary positions so we have to handle cropping by ourselves int cropLeft = _cropLeft; int cropRight = _cropRight; int cropTop = _cropTop; int cropBottom = _cropBottom; if (_vc_flags & DISPMANX_SNAPSHOT_FILL) { // disable cropping, we are capturing the video overlay window Debug(_log,"Disable cropping, as the video overlay window is captured"); cropLeft = cropRight = cropTop = cropBottom = 0; } unsigned imageWidth = static_cast(_width - cropLeft - cropRight); unsigned imageHeight = static_cast(_height - cropTop - cropBottom); // resize the given image if needed if (image.width() != imageWidth || image.height() != imageHeight) { image.resize(imageWidth, imageHeight); } if (_image_rgba.width() != imageWidth || _image_rgba.height() != imageHeight) { _image_rgba.resize(imageWidth, imageHeight); } // Open the connection to the display _vc_display = vc_dispmanx_display_open(DEFAULT_DEVICE); if (_vc_display < 0) { Error(_log, "Cannot open display: %d", DEFAULT_DEVICE); rc = -1; } else { // Create the snapshot (incl down-scaling) int ret = vc_dispmanx_snapshot(_vc_display, _vc_resource, _vc_flags); if (ret < 0) { Error(_log, "Snapshot failed: %d", ret); rc = ret; } else { // Read the snapshot into the memory void* imagePtr = _image_rgba.memptr(); void* capturePtr = imagePtr; unsigned imagePitch = imageWidth * sizeof(ColorRgba); // dispmanx seems to require the pitch to be a multiple of 64 unsigned capturePitch = (_rectangle.width * sizeof(ColorRgba) + 63) & (~63); // grab to temp buffer if image pitch isn't valid or if we are cropping if (imagePitch != capturePitch || static_cast(_rectangle.width) != imageWidth || static_cast(_rectangle.height) != imageHeight) { // check if we need to resize the capture buffer unsigned captureSize = capturePitch * static_cast(_rectangle.height) / sizeof(ColorRgba); if (_captureBufferSize != captureSize) { delete[] _captureBuffer; _captureBuffer = new ColorRgba[captureSize]; _captureBufferSize = captureSize; } capturePtr = &_captureBuffer[0]; } ret = vc_dispmanx_resource_read_data(_vc_resource, &_rectangle, capturePtr, capturePitch); if (ret < 0) { Error(_log, "vc_dispmanx_resource_read_data failed: %d", ret); rc = ret; } else { _imageResampler.processImage(static_cast(capturePtr), _width, _height, static_cast(capturePitch), PixelFormat::RGB32, image); } } vc_dispmanx_display_close(_vc_display); } } return rc; } QSize DispmanxFrameGrabber::getScreenSize(int device) const { int width (0); int height(0); DISPMANX_DISPLAY_HANDLE_T vc_display = vc_dispmanx_display_open(device); if ( vc_display > 0) { // Obtain the display information DISPMANX_MODEINFO_T vc_info; int result = vc_dispmanx_display_get_info(vc_display, &vc_info); (void)result; if (result == 0) { width = vc_info.width; height = vc_info.height; DebugIf(verbose, _log, "Display found with resolution: %dx%d", width, height); } // Close the display vc_dispmanx_display_close(vc_display); } return QSize(width, height); } QJsonObject DispmanxFrameGrabber::discover(const QJsonObject& params) { DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); QJsonObject inputsDiscovered; int deviceIdx (DEFAULT_DEVICE); QJsonArray video_inputs; QSize screenSize = getScreenSize(deviceIdx); if ( !screenSize.isEmpty() ) { QJsonArray fps = { 1, 5, 10, 15, 20, 25, 30, 40, 50, 60 }; QJsonObject in; QString displayName; displayName = QString("Screen:%1").arg(deviceIdx); in["name"] = displayName; in["inputIdx"] = deviceIdx; QJsonArray formats; QJsonObject format; QJsonArray resolutionArray; QJsonObject resolution; resolution["width"] = screenSize.width(); resolution["height"] = screenSize.height(); resolution["fps"] = fps; resolutionArray.append(resolution); format["resolutions"] = resolutionArray; formats.append(format); in["formats"] = formats; video_inputs.append(in); } if (!video_inputs.isEmpty()) { inputsDiscovered["device"] = "dispmanx"; inputsDiscovered["device_name"] = "DispmanX"; inputsDiscovered["type"] = "screen"; inputsDiscovered["video_inputs"] = video_inputs; QJsonObject defaults, video_inputs_default, resolution_default; resolution_default["fps"] = _fps; video_inputs_default["resolution"] = resolution_default; video_inputs_default["inputIdx"] = 0; defaults["video_input"] = video_inputs_default; inputsDiscovered["default"] = defaults; } if (inputsDiscovered.isEmpty()) { DebugIf(verbose, _log, "No displays found to capture from!"); } DebugIf(verbose, _log, "device: [%s]", QString(QJsonDocument(inputsDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData()); return inputsDiscovered; }