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:

    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: {
            CameraInterface.initCamera()
        }

        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;

    void 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. */
void CameraInterface::initCamera() {}

/* 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]));
        FAIL_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:

Images:

See also Qul::SharedImage, Qul::Image, Qul::EventQueue, and Qul::Singleton.

Available under certain Qt licenses.
Find out more.