C
Qt Quick Ultralite camera Example
Demonstrates how to integrate platform-specific image data with the QML UI.
Overview
This example demonstrates how to handle platform-specific image data. It has as simple user interface to control camera and access its frames using Qul::SharedImage. The example uses the camera hardware on the supported boards, and a dummy implementation for other boards.
Target platforms
Project structure
The example consists of a simple UI (camera.qml), camera interface definition (camerainterface.h), implementation of camera interface for NXP platforms, and a dummy implementation of the camera interface (in platform
directory). In addition, there is a background image asset (bg_qt.png).
CMake project files
In addtion to the Qt Quick Ultralite application's CMake setup, it includes the platform subdirectory containing the camera interface implementations for NXP and dummy.
add_subdirectory(platform)
In the QmlProject file, It also registers the camera interface so that Qt Quick Ultralite finds it.
InterfaceFiles { files: [ "camerainterface.h" ] }
The CMakeLists.txt in the platform directory checks the platform before choosing an implementation:
message(STATUS "Board manufacturer: ${BOARD_MANUFACTURER_NAME}") string(REGEX MATCHALL "([^\-]+|[^\-]+$)" PLATFORM_COMPONENTS ${QUL_PLATFORM}) list(GET PLATFORM_COMPONENTS 0 BOARD_NAME) string(TOLOWER ${BOARD_NAME} BOARD_NAME) if(NOT ${BOARD_NAME} STREQUAL "mimxrt1170" AND ${BOARD_MANUFACTURER_NAME} STREQUAL "nxp") add_subdirectory(nxp) else() add_subdirectory(dummy) endif()
The example depends on some code that is offered by the MCUXpresso SDK. Instead of adding that code to the Qt Quick Ultralite platform library, it is added to the example. This code mainly deals with CSI pin mux configuration and camera I2C functions. In addition, the camera support code and drivers are compiled in from the MCUXpresso SDK.
Application UI
The user interface is implemented in camera.qml.
The application starts by showing the background image and the Start Camera button. On clicking the button, the camera image stream is started and the Shoot and Stop buttons are enabled. Clicking the Shoot button stops the camera stream to the frame that was visible and enables the Minimize and Close buttons. The Minimize button minimizes the currently visible frame to the top-right corner and the Close button enables the previous state. Clicking the Stop button stops the camera stream and switches back to start state.
The interesting part of the user interface code is the cameraView
Image type:
Text { id: message color: "red" font.pixelSize: 14 } Image { id: cameraView anchors.right: parent.right anchors.top: parent.top source: CameraInterface.image width: 0 height: 0 Behavior on width { NumberAnimation { duration: 500; easing.type: Easing.OutCubic } } Behavior on height { NumberAnimation { duration: 500; easing.type: Easing.OutCubic } } Component.onCompleted: { if (!CameraInterface.initCamera()) { console.log("Camera initialization failed"); message.text = "Camera initialization failed.\nConnect a camera and/or check the serial console output."; } } SequentialAnimation on opacity { id: shootAnimation PropertyAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 200} PropertyAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 200} } }
It binds the image
property of the camera interface to its source
property. It also initializes the camera by calling initCamera()
from its event handler.
Camera interface
The camera interface is a simple Qul::Singleton interface with Qul::EventQueue. The event queue is used to deliver camera frames asynchronously from platform to the Qt Quick Ultralite application.
First, a simple struct that holds a pointer to the camera frame data is defined:
struct FrameEvent { uint8_t *newFrame; };
This struct is used as Qul::EventQueue's event data type.
Here is the camera interface definition:
struct CameraInterface : public Qul::Singleton<CameraInterface>, public Qul::EventQueue<FrameEvent> { Qul::Property<Qul::SharedImage> image; bool initCamera(); void startCamera(); void stopCamera(); private: void onEvent(const FrameEvent &frame) QUL_DECL_OVERRIDE; friend struct Qul::Singleton<CameraInterface>; CameraInterface() {} CameraInterface(const CameraInterface &); CameraInterface &operator=(const CameraInterface &); };
First, the camera interface introduces the image property of type, Qul::SharedImage. This property contains the frame data from the platform camera stream. After this, the functions to control the camera are declared. The initCamera() function to initialize the platform camera, and startCamera() and stopCamera() to start and stop the camera stream. The Qul::EventQueue's onEvent() function is overridden to handle image updates. Finally, external construction and copying is prevented.
Camera interface implementation
The implementations of camera interface are in the platform directory. The dummy subdirectory contains a dummy implementation, which implements stub functions of the interface. This can be used as starting point for porting the example to another platform:
#include "camerainterface.h" /* Initialize your platform's camera hardware in this function. */ bool CameraInterface::initCamera() { return false; } /* Start camera stream. */ void CameraInterface::startCamera() {} /* Stop camera stream. */ void CameraInterface::stopCamera() {} /* When the camera frame is ready, the image property can be updated in this event handler. */ void CameraInterface::onEvent(const FrameEvent &frameEvent)
The nxp subdirectory contains the implementation for NXP boards. The implementation uses the camera receiver API from NXP. It initializes the camera receiver in initCamera()
, which sets a callback function to receive new frames, and this
pointer as user data to allow posting events through the Qul::EventQueue.
err = CAMERA_RECEIVER_Init(&cameraReceiver, &cameraConfig, newCameraFrame, this);
Also empty camera frame buffers are submitted to the camera receiver.
for (uint32_t i = 0; i < CAMERA_BUFFER_COUNT; i++) { err = CAMERA_RECEIVER_SubmitEmptyBuffer(&cameraReceiver, (uint32_t) (s_cameraBuffers[i])); RETURN_FALSE_IF(err != kStatus_Success, "Failed to submit camera buffer!"); }
The camera receiver notifies about the new frames through the newCameraFrame
callback. The callback then posts a FrameEvent
containing pointer to the new camera frame.
me->postEventFromInterrupt(FrameEvent{(uint8_t *) cameraFrameAddr});
The FrameEvent
is handled in onEvent()
where Qul::Image with custom cleanup function is constructed, wrapped in Qul::SharedImage and set as the value of the image
property.
image.setValue(Qul::SharedImage(newFrame));
The cleanup
function is called when Qt Quick Ultralite engine destroys the Qul::SharedImage reference. The memory
argument of the function points to the buffer that was passed to the Qt Quick Ultralite engine using the image
property. The empty buffer is reused again to access the new camera frames whenever they are available.
status_t err = CAMERA_RECEIVER_SubmitEmptyBuffer(&cameraReceiver, (uint32_t) (memory));
Files:
- camera/CMakeLists.txt
- camera/camera.qml
- camera/camerainterface.h
- camera/mcu_camera.qmlproject
- camera/platform/CMakeLists.txt
- camera/platform/dummy/CMakeLists.txt
- camera/platform/dummy/camerainterface.cpp
- camera/platform/nxp/CMakeLists.txt
- camera/platform/nxp/camerainterface.cpp
- camera/platform/nxp/pin_mux_csi.c
- camera/platform/nxp/pin_mux_csi.h
Images:
See also Qul::SharedImage, Qul::Image, Qul::EventQueue, and Qul::Singleton.
Available under certain Qt licenses.
Find out more.