开源 C++ QT QML 开发(二十)多媒体--摄像头拍照

文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT QML 开发(一)基本介绍

开源 C++ QT QML 开发(二)工程结构

开源 C++ QT QML 开发(三)常用控件

开源 C++ QT QML 开发(四)复杂控件--Listview

开源 C++ QT QML 开发(五)复杂控件--Gridview

开源 C++ QT QML 开发(六)自定义控件--波形图

开源 C++ QT QML 开发(七)自定义控件--仪表盘

推荐链接:

开源 C# 快速开发(一)基础知识

开源 C# 快速开发(二)基础控件

开源 C# 快速开发(三)复杂控件

开源 C# 快速开发(四)自定义控件--波形图

开源 C# 快速开发(五)自定义控件--仪表盘

开源 C# 快速开发(六)自定义控件--圆环

开源 C# 快速开发(七)通讯--串口

开源 C# 快速开发(八)通讯--Tcp服务器端

开源 C# 快速开发(九)通讯--Tcp客户端

开源 C# 快速开发(十)通讯--http客户端

开源 C# 快速开发(十一)线程

开源 C# 快速开发(十二)进程监控

开源 C# 快速开发(十三)进程--管道通讯

开源 C# 快速开发(十四)进程--内存映射

开源 C# 快速开发(十五)进程--windows消息

开源 C# 快速开发(十六)数据库--sqlserver增删改查

本章节主要内容是:演示了基于qt和qml的多媒体应用中,笔记本电脑摄像头的拍照功能。

1.代码分析

2.所有源码

3.效果演示

一、代码分析

  1. Backend.h 详细分析

构造函数分析

复制代码
explicit Backend(QObject *parent = nullptr) : QObject(parent) {
    // 创建保存图片的目录
    QString picturesPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
    m_saveDir = QDir(picturesPath).filePath("CameraDiagnosticTool");
    if (!QDir().exists(m_saveDir)) {
        QDir().mkpath(m_saveDir);
    }
    qDebug() << "图片保存目录:" << m_saveDir;
}

功能细节:

QStandardPaths::writableLocation(QStandardPaths::PicturesLocation):获取系统标准的图片存储路径

Windows: C:\Users\Username\Pictures

macOS: /Users/Username/Pictures

Linux: /home/Username/Pictures

QDir(picturesPath).filePath("CameraDiagnosticTool"):构建应用专属目录路径

QDir().mkpath(m_saveDir):递归创建目录,确保父目录存在

输出目录信息便于调试

onImageCaptured 函数

复制代码
void onImageCaptured(const QString &previewPath) {
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    qDebug() << "[" << timestamp << "] 图像已捕获,预览路径:" << previewPath;
    emit statusMessageChanged("照片已捕获,显示预览");
}

参数分析:

const QString &previewPath:常量引用传递,避免拷贝开销

预览路径通常是临时文件路径

时间格式化:

"yyyy-MM-dd hh:mm:ss":年-月-日 时:分:秒 格式

使用24小时制建议改为 "yyyy-MM-dd HH:mm:ss"

onImageSaved 函数

复制代码
void onImageSaved(int requestId, const QString &filePath) {
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    qDebug() << "[" << timestamp << "] 图像已保存,请求ID:" << requestId << "路径:" << filePath;
    emit statusMessageChanged(QString("照片已保存到: %1").arg(filePath));
}

参数说明:

requestId:异步操作标识符,用于匹配请求和响应

filePath:最终保存的完整文件路径

showMessage 函数

复制代码
void showMessage(const QString &message) {
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    qDebug() << "[" << timestamp << "]" << message;
    emit statusMessageChanged(message);
}

设计模式:Facade模式,统一消息处理接口

captureImage 函数

复制代码
Q_INVOKABLE void captureImage(const QImage &image) {
    if (image.isNull()) {
        emit statusMessageChanged("错误: 捕获的图像为空");
        return;
    }

    // 生成文件名
    QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz");
    QString fileName = QString("capture_%1.jpg").arg(timestamp);
    QString filePath = QDir(m_saveDir).filePath(fileName);

    // 保存图片
    if (image.save(filePath, "JPG", 90)) {
        emit statusMessageChanged(QString("照片已保存: %1").arg(fileName));
        emit imageSaved(filePath);
        qDebug() << "图片保存成功:" << filePath;
    } else {
        emit statusMessageChanged("错误: 保存图片失败");
        qDebug() << "图片保存失败:" << filePath;
    }
}

关键点分析:

Q_INVOKABLE:使C++函数在QML中可调用

image.isNull():健壮性检查

时间戳格式:"yyyyMMdd_hhmmss_zzz" 包含毫秒,避免重名

图片质量:JPG格式,90%质量平衡文件大小和清晰度

双信号发射:状态消息 + 保存完成通知

getSaveDirectory 函数

复制代码
Q_INVOKABLE QString getSaveDirectory() const {
    return m_saveDir;
}

const 修饰符:线程安全,不会修改对象状态

简单的getter函数,提供目录信息给QML

  1. main.qml 详细分析

属性定义

复制代码
property string lastCapturedImage: ""
property var cameraList: QtMultimedia.availableCameras || []
property int currentCameraIndex: 0
属性分析:

lastCapturedImage:记录最后捕获的图片路径

cameraList:使用 || [] 提供默认值,防止undefined

currentCameraIndex:当前选中摄像头的索引

摄像头信息区域

复制代码
Label {
    text: {
        if (cameraList.length > 0) {
            var cam = cameraList[currentCameraIndex]
            return "当前摄像头: " + (cam.displayName || "未知摄像头")
        } else {
            return "未检测到摄像头"
        }
    }
    color: "#ecf0f1"
}

绑定表达式:使用JavaScript代码块动态计算文本内容

Camera 组件分析

复制代码
Camera {
    id: camera
    deviceId: cameraList.length > 0 ? cameraList[currentCameraIndex].deviceId : ""
    position: cameraList.length > 0 ? cameraList[currentCameraIndex].position : Camera.UnspecifiedPosition

    onError: {
        var errorMsg = "摄像头错误: " + errorString + " (错误代码: " + error + ")"
        console.log(errorMsg)
        backend.showMessage(errorMsg)
    }
}

错误处理机制:

errorString:人类可读的错误描述

error:错误代码,用于程序化处理

同时输出到控制台和用户界面

VideoOutput 中的图像捕获

复制代码
VideoOutput {
    id: videoOutput
    // ... 其他属性
    
    property var imageCapture: camera.imageCapture

    function captureImage() {
        if (camera.cameraStatus === Camera.ActiveStatus) {
            backend.showMessage("正在拍照...")
            camera.imageCapture.captureToLocation(backend.getSaveDirectory() + "/capture_" + 
                new Date().toLocaleString(Qt.locale(), "yyyyMMdd_hhmmss_zzz") + ".jpg")
        } else {
            backend.showMessage("错误: 请先启动摄像头")
        }
    }
}

关键函数:

camera.imageCapture:Qt 5.14中访问ImageCapture的方式

captureToLocation():直接保存到指定路径的异步方法

路径构建:与C++端保持一致的命名规则

状态覆盖层函数

qml

function getStatusText() {

switch(camera.cameraStatus) {

case Camera.ActiveStatus:

return "摄像头运行正常";

case Camera.StartingStatus:

return "摄像头启动中...";

case Camera.StoppingStatus:

return "摄像头停止中...";

case Camera.StandbyStatus:

return "摄像头待机中";

case Camera.LoadedStatus:

return "摄像头已加载";

case Camera.LoadingStatus:

return "摄像头加载中...";

case Camera.UnloadingStatus:

return "摄像头卸载中...";

case Camera.UnloadedStatus:

return "摄像头未加载";

case Camera.UnavailableStatus:

return "摄像头不可用";

default:

return "摄像头状态未知: " + camera.cameraStatus;

}

}

状态机处理:完整覆盖Camera的所有可能状态信号连接系统

// 连接后端信号

复制代码
Connections {
    target: backend
    onStatusMessageChanged: {
        var timestamp = new Date().toLocaleTimeString()
        statusText.text = "[" + timestamp + "] " + message + "\n" + statusText.text
    }
    
    onImageSaved: {
        photoPreview.source = "file:///" + filePath
    }
}

// 连接摄像头图像捕获信号
Connections {
    target: camera.imageCapture
    onImageCaptured: {
        photoPreview.source = preview
        backend.onImageCaptured(preview)
    }
    onImageSaved: {
        backend.onImageSaved(requestId, path)
        backend.showMessage("照片已保存: " + path)
    }
}

多目标连接:

后端状态消息:更新日志显示

图像保存完成:更新预览图片

摄像头捕获事件:处理预览和保存

组件初始化

复制代码
Component.onCompleted: {
    backend.showMessage("应用程序启动完成")
    backend.showMessage("检测到 " + cameraList.length + " 个摄像头")
    backend.showMessage("照片保存目录: " + backend.getSaveDirectory())

    if (cameraList.length === 0) {
        backend.showMessage("警告: 未检测到任何摄像头设备")
        backend.showMessage("请检查摄像头连接和驱动程序")
    } else {
        for (var i = 0; i < cameraList.length; i++) {
            var cam = cameraList[i]
            backend.showMessage("摄像头 " + i + ": " + (cam.displayName || "未知") +
                              " (位置: " + getPositionName(cam.position) + ")")
        }
    }
}

启动流程:

应用启动通知

摄像头数量检测

目录信息显示

设备详情枚举

  1. main.cpp 详细分析

主函数结构

复制代码
int main(int argc, char *argv[])
{
    // 1. 应用程序属性设置
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
    // 2. 应用程序实例化
    QGuiApplication app(argc, argv);

    // 3. 应用程序元数据
    app.setApplicationName("笔记本电脑摄像头诊断工具");
    app.setApplicationVersion("1.1");
    app.setOrganizationName("YourCompany");

    // 4. 后端对象创建
    Backend backend;

    // 5. QML引擎初始化
    QQmlApplicationEngine engine;

    // 6. 上下文属性设置
    engine.rootContext()->setContextProperty("backend", &backend);

    // 7. QML文件加载
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    // 8. 启动检查
    if (engine.rootObjects().isEmpty()) {
        return -1;
    }

    // 9. 调试信息输出
    qDebug() << "照片保存目录:" << backend.getSaveDirectory();

    // 10. 事件循环启动
    return app.exec();
}

关键技术点分析

高DPI支持:

QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

自动适配高分辨率显示器

解决在高DPI设备上界面元素过小的问题

应用程序元数据:

app.setApplicationName("笔记本电脑摄像头诊断工具");

app.setApplicationVersion("1.1");

app.setOrganizationName("YourCompany");

用于系统集成(如设置存储、关于对话框等)

遵循Qt应用程序最佳实践

上下文属性绑定:

engine.rootContext()->setContextProperty("backend", &backend);

将C++对象注入QML上下文

在QML中通过 backend 标识符访问

资源加载机制:

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

使用Qt资源系统(qrc)

编译时嵌入资源,避免运行时文件依赖

健全性检查:

if (engine.rootObjects().isEmpty()) {

return -1;

}

确保QML文件正确加载

防止空窗口应用程序

整体架构总结

数据流向

text

用户操作 → QML界面 → 信号发射 → C++后端处理 → 文件系统操作

状态反馈 ← 信号发射 ← QML更新 ←───┘

二、所有源码

.pro文件添加

复制代码
QT += quick quickcontrols2 multimedia

backend.h文件源码

复制代码
#ifndef BACKEND_H
#define BACKEND_H

#include <QObject>
#include <QString>
#include <QDebug>
#include <QDateTime>
#include <QImage>
#include <QDir>
#include <QStandardPaths>

class Backend : public QObject
{
    Q_OBJECT

public:
    explicit Backend(QObject *parent = nullptr) : QObject(parent) {
        // 创建保存图片的目录
        QString picturesPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
        m_saveDir = QDir(picturesPath).filePath("CameraDiagnosticTool");
        if (!QDir().exists(m_saveDir)) {
            QDir().mkpath(m_saveDir);
        }
        qDebug() << "图片保存目录:" << m_saveDir;
    }

public slots:
    // 处理图像捕获事件
    void onImageCaptured(const QString &previewPath) {
        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        qDebug() << "[" << timestamp << "] 图像已捕获,预览路径:" << previewPath;
        emit statusMessageChanged("照片已捕获,显示预览");
    }

    // 处理图像保存事件
    void onImageSaved(int requestId, const QString &filePath) {
        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        qDebug() << "[" << timestamp << "] 图像已保存,请求ID:" << requestId << "路径:" << filePath;
        emit statusMessageChanged(QString("照片已保存到: %1").arg(filePath));
    }

    // 显示消息
    void showMessage(const QString &message) {
        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        qDebug() << "[" << timestamp << "]" << message;
        emit statusMessageChanged(message);
    }

    // 拍照并保存
    Q_INVOKABLE void captureImage(const QImage &image) {
        if (image.isNull()) {
            emit statusMessageChanged("错误: 捕获的图像为空");
            return;
        }

        // 生成文件名
        QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz");
        QString fileName = QString("capture_%1.jpg").arg(timestamp);
        QString filePath = QDir(m_saveDir).filePath(fileName);

        // 保存图片
        if (image.save(filePath, "JPG", 90)) {
            emit statusMessageChanged(QString("照片已保存: %1").arg(fileName));
            emit imageSaved(filePath);
            qDebug() << "图片保存成功:" << filePath;
        } else {
            emit statusMessageChanged("错误: 保存图片失败");
            qDebug() << "图片保存失败:" << filePath;
        }
    }

    // 获取保存目录
    Q_INVOKABLE QString getSaveDirectory() const {
        return m_saveDir;
    }

signals:
    void statusMessageChanged(const QString &message);
    void imageSaved(const QString &filePath);

private:
    QString m_saveDir;
};

#endif // BACKEND_H

main.qml文件源码

复制代码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtMultimedia 5.12

ApplicationWindow {
    id: mainWindow
    width: 800
    height: 600
    title: "笔记本电脑摄像头诊断工具"
    visible: true

    property string lastCapturedImage: ""
    property var cameraList: QtMultimedia.availableCameras || []
    property int currentCameraIndex: 0

    // 背景
    Rectangle {
        anchors.fill: parent
        gradient: Gradient {
            GradientStop { position: 0.0; color: "#2c3e50" }
            GradientStop { position: 1.0; color: "#34495e" }
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10
        spacing: 10

        // 标题
        Label {
            text: "摄像头诊断工具"
            Layout.alignment: Qt.AlignHCenter
            color: "white"
            font.pixelSize: 24
            font.bold: true
        }

        // 摄像头信息区域
        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 100
            color: "#34495e"
            radius: 5
            border.color: "#7f8c8d"

            ColumnLayout {
                anchors.fill: parent
                anchors.margins: 10

                Label {
                    text: "摄像头信息:"
                    color: "white"
                    font.bold: true
                }

                Label {
                    text: "检测到的摄像头数量: " + cameraList.length
                    color: "#ecf0f1"
                }

                Label {
                    text: {
                        if (cameraList.length > 0) {
                            var cam = cameraList[currentCameraIndex]
                            return "当前摄像头: " + (cam.displayName || "未知摄像头")
                        } else {
                            return "未检测到摄像头"
                        }
                    }
                    color: "#ecf0f1"
                }

                // 摄像头选择(如果有多个摄像头)
                ComboBox {
                    id: cameraSelector
                    Layout.fillWidth: true
                    model: cameraList
                    visible: cameraList.length > 1
                    textRole: "displayName"

                    onCurrentIndexChanged: {
                        if (camera.cameraStatus === Camera.ActiveStatus) {
                            camera.stop()
                        }
                        currentCameraIndex = currentIndex
                        backend.showMessage("已选择摄像头: " + currentText)
                    }
                }
            }
        }

        // 摄像头预览区域
        Rectangle {
            id: previewContainer
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#1a1a1a"
            border.color: camera.cameraStatus === Camera.ActiveStatus ? "#27ae60" : "#e74c3c"
            border.width: 2
            radius: 8

            Camera {
                id: camera
                deviceId: cameraList.length > 0 ? cameraList[currentCameraIndex].deviceId : ""
                position: cameraList.length > 0 ? cameraList[currentCameraIndex].position : Camera.UnspecifiedPosition

                onError: {
                    var errorMsg = "摄像头错误: " + errorString + " (错误代码: " + error + ")"
                    console.log(errorMsg)
                    backend.showMessage(errorMsg)
                }

                onCameraStateChanged: {
                    var stateMsg = "摄像头状态: "
                    switch(camera.cameraState) {
                    case Camera.ActiveState:
                        stateMsg += "运行中";
                        break;
                    case Camera.LoadedState:
                        stateMsg += "已加载";
                        break;
                    case Camera.UnloadedState:
                        stateMsg += "未加载";
                        break;
                    default:
                        stateMsg += "未知状态";
                    }
                    backend.showMessage(stateMsg)
                }

                onCameraStatusChanged: {
                    var statusMsg = "摄像头状态码: " + camera.cameraStatus
                    backend.showMessage(statusMsg)
                }
            }

            VideoOutput {
                id: videoOutput
                anchors.fill: parent
                anchors.margins: 5
                source: camera
                fillMode: VideoOutput.PreserveAspectCrop
                focus: visible

                // 在 Qt 5.x 中,ImageCapture 需要这样使用
                property var imageCapture: camera.imageCapture

                function captureImage() {
                    if (camera.cameraStatus === Camera.ActiveStatus) {
                        backend.showMessage("正在拍照...")
                        camera.imageCapture.captureToLocation(backend.getSaveDirectory() + "/capture_" +
                            new Date().toLocaleString(Qt.locale(), "yyyyMMdd_hhmmss_zzz") + ".jpg")
                    } else {
                        backend.showMessage("错误: 请先启动摄像头")
                    }
                }
            }

            // 状态覆盖层
            Rectangle {
                anchors.fill: parent
                color: "#80000000"
                visible: camera.cameraStatus !== Camera.ActiveStatus

                Column {
                    anchors.centerIn: parent
                    spacing: 10

                    Label {
                        text: getStatusText()
                        color: "white"
                        font.pixelSize: 16
                        anchors.horizontalCenter: parent.horizontalCenter
                    }

                    BusyIndicator {
                        running: camera.cameraStatus === Camera.StartingStatus
                        anchors.horizontalCenter: parent.horizontalCenter
                        visible: running
                    }
                }
            }

            function getStatusText() {
                switch(camera.cameraStatus) {
                case Camera.ActiveStatus:
                    return "摄像头运行正常";
                case Camera.StartingStatus:
                    return "摄像头启动中...";
                case Camera.StoppingStatus:
                    return "摄像头停止中...";
                case Camera.StandbyStatus:
                    return "摄像头待机中";
                case Camera.LoadedStatus:
                    return "摄像头已加载";
                case Camera.LoadingStatus:
                    return "摄像头加载中...";
                case Camera.UnloadingStatus:
                    return "摄像头卸载中...";
                case Camera.UnloadedStatus:
                    return "摄像头未加载";
                case Camera.UnavailableStatus:
                    return "摄像头不可用";
                default:
                    return "摄像头状态未知: " + camera.cameraStatus;
                }
            }
        }

        // 控制按钮区域
        RowLayout {
            Layout.alignment: Qt.AlignHCenter
            spacing: 20

            Button {
                id: startButton
                text: "启动摄像头"
                Layout.preferredWidth: 140
                Layout.preferredHeight: 40

                background: Rectangle {
                    color: parent.down ? "#27ae60" :
                           parent.hovered ? "#2ecc71" : "#27ae60"
                    radius: 5
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.pixelSize: 14
                    font.bold: true
                }

                onClicked: {
                    backend.showMessage("正在启动摄像头...")
                    console.log("尝试启动摄像头,设备ID:", camera.deviceId)
                    camera.start()
                }
            }

            Button {
                id: stopButton
                text: "停止摄像头"
                Layout.preferredWidth: 140
                Layout.preferredHeight: 40

                background: Rectangle {
                    color: parent.down ? "#c0392b" :
                           parent.hovered ? "#e74c3c" : "#c0392b"
                    radius: 5
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.pixelSize: 14
                    font.bold: true
                }

                onClicked: {
                    camera.stop()
                    backend.showMessage("摄像头已停止")
                }
            }

            // 拍照按钮
            Button {
                id: captureButton
                text: "拍照"
                Layout.preferredWidth: 140
                Layout.preferredHeight: 40
                enabled: camera.cameraStatus === Camera.ActiveStatus

                background: Rectangle {
                    color: parent.down ? "#f39c12" :
                           parent.hovered ? "#f1c40f" :
                           parent.enabled ? "#f39c12" : "#95a5a6"
                    radius: 5
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.pixelSize: 14
                    font.bold: true
                }

                onClicked: {
                    videoOutput.captureImage()
                }
            }

            Button {
                id: refreshButton
                text: "刷新摄像头列表"
                Layout.preferredWidth: 140
                Layout.preferredHeight: 40

                background: Rectangle {
                    color: parent.down ? "#2980b9" :
                           parent.hovered ? "#3498db" : "#2980b9"
                    radius: 5
                }
                contentItem: Text {
                    text: parent.text
                    color: "white"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    font.pixelSize: 14
                    font.bold: true
                }

                onClicked: {
                    // 强制刷新摄像头列表
                    cameraList = QtMultimedia.availableCameras || []
                    backend.showMessage("已刷新摄像头列表,检测到 " + cameraList.length + " 个摄像头")
                }
            }
        }

        // 图片预览区域
        Rectangle {
            id: previewSection
            Layout.fillWidth: true
            Layout.preferredHeight: 150
            color: "transparent"
            visible: photoPreview.source !== ""

            Label {
                text: "最新照片预览:"
                color: "white"
                font.bold: true
                anchors.top: parent.top
                anchors.left: parent.left
            }

            RowLayout {
                anchors.fill: parent
                anchors.topMargin: 25
                spacing: 10

                // 图片预览
                Image {
                    id: photoPreview
                    Layout.preferredWidth: 120
                    Layout.preferredHeight: 120
                    fillMode: Image.PreserveAspectFit
                    cache: false
                }

                // 照片信息
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 5

                    Label {
                        text: "尺寸: " + (photoPreview.sourceSize.width + "x" + photoPreview.sourceSize.height)
                        color: "#ecf0f1"
                        font.pixelSize: 12
                    }

                    Label {
                        text: "保存目录:"
                        color: "#ecf0f1"
                        font.pixelSize: 12
                        font.bold: true
                    }

                    Label {
                        text: backend.getSaveDirectory()
                        color: "#bdc3c7"
                        font.pixelSize: 10
                        wrapMode: Text.Wrap
                        Layout.maximumWidth: 400
                    }

                    Button {
                        text: "打开保存文件夹"
                        Layout.preferredWidth: 120
                        Layout.preferredHeight: 30
                        visible: Qt.platform.os !== "android" && Qt.platform.os !== "ios"

                        background: Rectangle {
                            color: parent.down ? "#16a085" :
                                   parent.hovered ? "#1abc9c" : "#16a085"
                            radius: 3
                        }
                        contentItem: Text {
                            text: parent.text
                            color: "white"
                            horizontalAlignment: Text.AlignHCenter
                            verticalAlignment: Text.AlignVCenter
                            font.pixelSize: 11
                        }

                        onClicked: {
                            Qt.openUrlExternally("file:///" + backend.getSaveDirectory())
                        }
                    }
                }
            }
        }

        // 诊断信息区域
        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 80
            color: "#2c3e50"
            radius: 5
            border.color: "#7f8c8d"

            ScrollView {
                anchors.fill: parent
                anchors.margins: 5

                TextArea {
                    id: statusText
                    readOnly: true
                    wrapMode: Text.Wrap
                    color: "#ecf0f1"
                    font.pixelSize: 11
                    background: null
                    text: "应用程序已启动\n" +
                          "正在初始化摄像头系统..."
                }
            }
        }
    }

    // 连接后端信号
    Connections {
        target: backend
        onStatusMessageChanged: {
            var timestamp = new Date().toLocaleTimeString()
            statusText.text = "[" + timestamp + "] " + message + "\n" + statusText.text
        }

        onImageSaved: {
            photoPreview.source = "file:///" + filePath
        }
    }

    // 连接摄像头图像捕获信号
    Connections {
        target: camera.imageCapture
        onImageCaptured: {
            photoPreview.source = preview
            backend.onImageCaptured(preview)
        }
        onImageSaved: {
            backend.onImageSaved(requestId, path)
            backend.showMessage("照片已保存: " + path)
        }
    }

    // 应用启动时自动检测
    Component.onCompleted: {
        backend.showMessage("应用程序启动完成")
        backend.showMessage("检测到 " + cameraList.length + " 个摄像头")
        backend.showMessage("照片保存目录: " + backend.getSaveDirectory())

        if (cameraList.length === 0) {
            backend.showMessage("警告: 未检测到任何摄像头设备")
            backend.showMessage("请检查摄像头连接和驱动程序")
        } else {
            for (var i = 0; i < cameraList.length; i++) {
                var cam = cameraList[i]
                backend.showMessage("摄像头 " + i + ": " + (cam.displayName || "未知") +
                                  " (位置: " + getPositionName(cam.position) + ")")
            }
        }
    }

    function getPositionName(position) {
        switch(position) {
        case Camera.BackFace: return "后置";
        case Camera.FrontFace: return "前置";
        case Camera.UnspecifiedPosition: return "未指定";
        default: return "未知";
        }
    }

    // 应用退出时停止摄像头
    onClosing: {
        camera.stop()
    }
}

main.cpp文件源码

复制代码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QIcon>
#include "backend.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    // 设置应用程序信息
    app.setApplicationName("笔记本电脑摄像头诊断工具");
    app.setApplicationVersion("1.1");
    app.setOrganizationName("YourCompany");

    // 注册后端类
    Backend backend;

    QQmlApplicationEngine engine;

    // 将后端对象暴露给QML
    engine.rootContext()->setContextProperty("backend", &backend);

    // 加载QML文件
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    if (engine.rootObjects().isEmpty()) {
        return -1;
    }

    // 显示保存目录信息
    qDebug() << "照片保存目录:" << backend.getSaveDirectory();

    return app.exec();
}

三、效果演示

点击启动摄像头,进行拍照,照片自动保存,同时具有预览功能。

相关推荐
_OP_CHEN3 小时前
C++基础:(十二)list类的基础使用
开发语言·数据结构·c++·stl·list类·list核心接口·list底层原理
2501_915921434 小时前
iOS 是开源的吗?苹果系统的封闭与开放边界全解析(含开发与开心上架(Appuploader)实战)
android·ios·小程序·uni-app·开源·iphone·webview
西京刀客4 小时前
开源 ETL(Extract,Transform,Load)工具之Apache Hop
开源·apache·etl·hop
晚风残6 小时前
【C++ Primer】第六章:函数
开发语言·c++·算法·c++ primer
满天星83035776 小时前
【C++】AVL树的模拟实现
开发语言·c++·算法·stl
CoderJia程序员甲7 小时前
GitHub 热榜项目 - 日榜(2025-10-11)
ai·开源·github·ai编程·github热榜
Mr_WangAndy7 小时前
C++设计模式_行为型模式_责任链模式Chain of Responsibility
c++·设计模式·责任链模式·行为型模式
时间之里7 小时前
【c++】:Lambda 表达式介绍和使用
开发语言·c++
汉克老师8 小时前
GESP2025年9月认证C++四级( 第三部分编程题(1)排兵布阵)
c++·算法·gesp4级·gesp四级