开源 C++ QT QML 开发(二十二)多媒体--ffmpeg编码和录像

文章的目的为了记录使用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++ QT QML 开发(八)自定义控件--圆环

开源 C++ QT QML 开发(九)文件--文本和二进制

开源 C++ QT QML 开发(十)通讯--串口

开源 C++ QT QML 开发(十一)通讯--TCP服务器端

开源 C++ QT QML 开发(十二)通讯--TCP客户端

开源 C++ QT QML 开发(十三)多线程

开源 C++ QT QML 开发(十四)进程用途

开源 C++ QT QML 开发(十五)通讯--http下载

开源 C++ QT QML 开发(十六)进程--共享内存

开源 C++ QT QML 开发(十七)进程--LocalSocket

开源 C++ QT QML 开发(十八)多媒体--音频播放

开源 C++ QT QML 开发(十九)多媒体--音频录制

开源 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增删改查

本章节主要内容是:一个使用外部FFmpeg进程进行摄像头录制的Qt QML应用程序。

1.代码分析

2.所有源码

3.效果演示

一、代码分析

QML部分函数分析

  1. 初始化函数

    Component.onCompleted: {
    console.log("初始化摄像头...")
    if (QtMultimedia.availableCameras.length > 0) {
    camera.start()
    statusMessage = "摄像头就绪 - 点击开始录制"
    } else {
    statusMessage = "未检测到摄像头"
    startButton.enabled = false
    }
    }

功能:应用程序启动时自动执行

检查系统可用摄像头数量

启动第一个可用摄像头

设置初始状态消息

无摄像头时禁用开始按钮

  1. 开始录制函数

    function startRecording() {
    var timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)
    var fileName = "ffmpeg_record_" + timestamp + ".mp4"
    var savePath = "C:/Users/Administrator/Desktop/" + fileName

    复制代码
     console.log("准备开始录制:", savePath)
    
     // 先停止摄像头预览
     stopCameraPreview()
    
     // 延迟启动FFmpeg录制
     startRecordingTimer.savedPath = savePath
     startRecordingTimer.start()

    }

功能:启动录制流程

生成带时间戳的文件名

构建完整保存路径

停止Qt摄像头预览释放设备

启动延迟定时器

参数处理:

timestamp: 格式化为 2025-10-16T15-10-02

fileName: 固定前缀 + 时间戳 + .mp4后缀

savePath: 硬编码桌面路径

  1. 停止摄像头预览函数

    function stopCameraPreview() {
    console.log("停止摄像头预览")
    cameraActive = false
    camera.stop()
    }

功能:释放摄像头资源

设置 cameraActive = false 隐藏视频预览

调用 camera.stop() 释放硬件设备

  1. 停止录制函数

    function stopRecording() {
    console.log("停止FFmpeg录制")
    ffmpegRecorder.stopRecording()
    }

功能:委托给C++后端停止录制

简单的代理函数

调用C++的 stopRecording() 方法

  1. 状态文本转换函数

    function getCameraStatusText(status) {
    switch(status) {
    case Camera.ActiveStatus: return "活动"
    case Camera.LoadingStatus: return "加载中"
    case Camera.StartingStatus: return "启动中"
    case Camera.StoppingStatus: return "停止中"
    case Camera.StandbyStatus: return "待机"
    case Camera.UnavailableStatus: return "不可用"
    default: return "未知"
    }
    }

功能:将摄像头状态码转换为可读文本

处理6种标准摄像头状态

提供默认"未知"状态处理

  1. 时间格式化函数

    function formatTime(seconds) {
    var hours = Math.floor(seconds / 3600)
    var minutes = Math.floor((seconds % 3600) / 60)
    var secs = seconds % 60
    return (hours < 10 ? "0" + hours : hours) + ":" +
    (minutes < 10 ? "0" + minutes : minutes) + ":" +
    (secs < 10 ? "0" + secs : secs)
    }

功能:将秒数格式化为 HH:MM:SS

数学计算时分秒

补零格式化(01:05:09)

支持超过24小时的显示

  1. 定时器更新函数

    function updateTimerDisplay() {
    timerText.text = "录制时间: " + formatTime(recordingSeconds)
    }

功能:更新界面计时器显示

组合文本和时间格式

每秒调用一次

C++部分函数分析

  1. 构造函数

    explicit FFmpegRecorder(QObject *parent = nullptr)
    : QObject(parent), m_isRecording(false) {}

功能:初始化录制器

设置父对象

初始化录制状态为false

  1. 属性读取函数

    bool isRecording() const { return m_isRecording; }
    QString statusMessage() const { return m_statusMessage; }

功能:QML属性绑定支持

提供只读属性访问

用于QML的数据绑定

  1. 开始录制函数(QML可调用)

    Q_INVOKABLE void startRecording(const QString &outputPath) {
    if (m_isRecording) {
    setStatusMessage("已经在录制中");
    return;
    }

    复制代码
     m_outputPath = outputPath;
     startFFmpegRecording(outputPath);

    }

功能:录制入口点

检查重复录制

保存输出路径

调用内部录制函数

  1. 停止录制函数(QML可调用)

    Q_INVOKABLE void stopRecording() {
    if (m_process && m_isRecording) {
    // 向FFmpeg发送q信号来优雅停止
    m_process->write("q");
    m_process->closeWriteChannel();
    setStatusMessage("正在停止录制...");
    }
    }

功能:优雅停止FFmpeg

向FFmpeg进程发送"q"信号

关闭写入通道

更新状态消息

  1. 内部FFmpeg启动函数

    void startFFmpegRecording(const QString &outputPath) {
    QString ffmpegPath = "C:/ffmpeg/bin/ffmpeg.exe";

    复制代码
     QStringList arguments;
     arguments << "-f" << "dshow" 
               << "-i" << "video=Integrated Camera" 
               << "-t" << "300"  // 录制5分钟
               << "-y"  // 覆盖已存在文件
               << QDir::toNativeSeparators(outputPath);
    
     qDebug() << "启动FFmpeg录制...";
    
     m_process = new QProcess(this);
     
     // 连接信号槽
     connect(m_process, &QProcess::started, this, [this]() {
         m_isRecording = true;
         setStatusMessage("正在录制中...");
         emit isRecordingChanged();
     });
    
     // ... 其他连接
    
     m_process->start(ffmpegPath, arguments);
     
     if (!m_process->waitForStarted(3000)) {
         setStatusMessage("启动FFmpeg失败: " + m_process->errorString());
         // 清理资源
     }

    }

功能:核心录制逻辑

构建FFmpeg命令行参数

创建并配置QProcess

连接进程信号

启动进程并处理超时

FFmpeg参数详解:

-f dshow: Windows DirectShow输入格式

-i video=Integrated Camera: 指定摄像头设备

-t 300: 录制300秒(5分钟)

-y: 覆盖输出文件

outputPath: 输出文件路径

  1. 进程完成处理函数

    void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
    qDebug() << "FFmpeg进程结束,退出码:" << exitCode;

    复制代码
     m_isRecording = false;
     
     if (exitCode == 0) {
         setStatusMessage("录制完成: " + m_outputPath);
     } else {
         setStatusMessage("录制失败,退出码: " + QString::number(exitCode));
     }
     
     if (m_process) {
         m_process->deleteLater();
         m_process = nullptr;
     }
     
     emit isRecordingChanged();

    }

功能:处理FFmpeg进程结束

解析退出码判断成功/失败

清理进程资源

通知QML状态变化

  1. 进程错误处理函数

    void onProcessError(QProcess::ProcessError error) {
    qDebug() << "FFmpeg进程错误:" << error;

    复制代码
     m_isRecording = false;
     setStatusMessage("FFmpeg错误: " + QString::number(error));
     
     if (m_process) {
         m_process->deleteLater();
         m_process = nullptr;
     }
     emit isRecordingChanged();

    }

功能:处理进程启动/运行错误

记录错误类型

重置录制状态

清理资源

  1. 状态消息设置函数

    void setStatusMessage(const QString &message) {
    if (m_statusMessage != message) {
    m_statusMessage = message;
    emit statusMessageChanged();
    }
    }

功能:状态消息管理

避免重复设置相同消息

触发属性变化信号

定时器功能分析

  1. 录制计时器

    Timer {
    id: recordTimer
    interval: 1000
    repeat: true
    running: ffmpegRecorder.isRecording
    onTriggered: {
    recordingSeconds++
    updateTimerDisplay()
    }
    }

功能:录制时长计数

每秒触发一次

仅在录制时运行

累加录制秒数

  1. 延迟启动定时器

    Timer {
    id: startRecordingTimer
    property string savedPath: ""
    interval: 1000
    onTriggered: {
    console.log("延迟启动FFmpeg录制:", savedPath)
    ffmpegRecorder.startRecording(savedPath)
    recordingSeconds = 0
    updateTimerDisplay()
    }
    }

功能:确保摄像头完全释放

1秒延迟避免设备冲突

保存路径传递

重置计时器

  1. 预览重启定时器

    Timer {
    id: restartPreviewTimer
    interval: 500
    onTriggered: {
    console.log("重新启动摄像头预览")
    cameraActive = true
    if (camera.cameraStatus !== Camera.ActiveStatus) {
    camera.start()
    }
    }
    }

功能:录制完成后恢复预览

500ms延迟确保FFmpeg完全退出

重新激活摄像头

条件启动避免重复调用

信号连接分析

复制代码
Connections {
    target: ffmpegRecorder
    
    function onStopCameraPreview() {
        console.log("停止摄像头预览")
        stopCameraPreview()
    }
    
    function onStartCameraPreview() {
        console.log("启动摄像头预览")
        restartPreviewTimer.start()
    }
}

功能:C++到QML的通信桥梁

响应C++发出的控制信号

但当前代码中C++并未实际发出这些信号(代码不完整)

二、所有源码

在Qt中使用摄像头进行录像,通常涉及到视频捕获和编码处理。FFmpeg是一个非常强大的开源库,它提供了广泛的视频和音频格式的支持,包括编码、解码、转码、录制等功能。在Qt中使用FFmpeg来实现摄像头录像的功能,有几个关键原因:

‌广泛的格式支持‌:FFmpeg支持几乎所有常见的视频和音频格式,包括但不限于H.264、H.265、VP8、VP9等。这使得使用FFmpeg可以很容易地录制多种格式的视频。

‌硬件加速‌:FFmpeg支持多种硬件加速技术,如NVENC(用于NVIDIA显卡)、VA-API(用于Intel显卡)和AMD的VCE。这些硬件加速可以显著提高视频编码的效率,减少CPU的负担,尤其是在处理高清视频时。

‌灵活的API‌:FFmpeg提供了丰富的API,允许开发者以编程方式控制几乎所有的视频处理功能。这包括设置视频编码参数(如比特率、帧率、分辨率等)、音频处理等。

‌跨平台‌:FFmpeg是跨平台的,可以在Windows、macOS、Linux等多种操作系统上运行。这使得使用FFmpeg开发的视频处理应用具有很好的移植性。

‌集成到Qt中‌:虽然Qt本身提供了视频和相机相关的类(如QCamera和QCameraImageCapture),但这些类在某些复杂场景下可能不够用,或者需要额外的功能支持。通过集成FFmpeg,可以扩展Qt应用在视频处理方面的能力。

安装FFmpeg(必要步骤):

下载FFmpeg:https://www.gyan.dev/ffmpeg/builds/

解压到 C:\ffmpeg\

将 C:\ffmpeg\bin 添加到系统PATH环境变量

重启电脑

.pro文件需要添加

复制代码
QT += quick quickcontrols2 multimedia multimediawidgets

main.qml文件源码

复制代码
import QtQuick 2.14
import QtQuick.Window 2.14
import QtMultimedia 5.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14

ApplicationWindow {
    id: window
    width: 800
    height: 600
    visible: true
    title: qsTr("摄像头录像 - FFmpeg外部进程")

    property int recordingSeconds: 0
    property string statusMessage: "正在初始化..."
    property bool cameraActive: true

    // 摄像头组件
    Camera {
        id: camera
        deviceId: QtMultimedia.availableCameras.length > 0 ? QtMultimedia.availableCameras[0].deviceId : ""

        onError: {
            console.error("摄像头错误:", errorString)
            statusMessage = "摄像头错误: " + errorString
        }

        onCameraStatusChanged: {
            console.log("摄像头状态:", cameraStatus)
            if (cameraStatus === Camera.ActiveStatus) {
                statusMessage = "摄像头就绪 - 点击开始录制"
            }
        }
    }

    // 视频输出 - 只在摄像头激活时显示
    VideoOutput {
        id: videoOutput
        anchors.fill: parent
        source: camera
        visible: cameraActive
    }

    // 录制时的占位背景
    Rectangle {
        anchors.fill: parent
        color: "black"
        visible: !cameraActive

        Column {
            anchors.centerIn: parent
            spacing: 20

            Text {
                text: "● 正在录制中"
                color: "#e74c3c"
                font.pixelSize: 32
                font.bold: true
            }

            Text {
                text: timerText.text
                color: "white"
                font.pixelSize: 24
                font.family: "Monospace"
            }

            Text {
                text: "录制完成后将自动恢复预览"
                color: "#aaaaaa"
                font.pixelSize: 16
            }
        }
    }

    // 控制面板
    Rectangle {
        width: parent.width
        height: 160
        anchors.bottom: parent.bottom
        color: "#CC000000"

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

            // 状态显示
            Text {
                Layout.fillWidth: true
                text: ffmpegRecorder.statusMessage || statusMessage
                color: ffmpegRecorder.isRecording ? "#e74c3c" : "white"
                font.pixelSize: 14
                wrapMode: Text.Wrap
            }

            // 录制时间
            Text {
                id: timerText
                Layout.fillWidth: true
                text: "录制时间: " + formatTime(recordingSeconds)
                color: "#e74c3c"
                font.bold: true
                font.pixelSize: 18
                font.family: "Monospace"
                visible: ffmpegRecorder.isRecording
            }

            // 按钮行
            RowLayout {
                Layout.fillWidth: true
                spacing: 15

                Button {
                    id: startButton
                    text: "● 开始录制"
                    enabled: !ffmpegRecorder.isRecording && cameraActive
                    onClicked: startRecording()

                    background: Rectangle {
                        color: startButton.enabled ? "#27ae60" : "#666666"
                        radius: 5
                    }

                    contentItem: Text {
                        text: startButton.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.bold: true
                    }

                    Layout.preferredWidth: 120
                    Layout.preferredHeight: 40
                }

                Button {
                    id: stopButton
                    text: "■ 停止录制"
                    enabled: ffmpegRecorder.isRecording
                    onClicked: stopRecording()

                    background: Rectangle {
                        color: stopButton.enabled ? "#e74c3c" : "#666666"
                        radius: 5
                    }

                    contentItem: Text {
                        text: stopButton.text
                        color: "white"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.bold: true
                    }

                    Layout.preferredWidth: 120
                    Layout.preferredHeight: 40
                }

                // 录制指示器
                Rectangle {
                    width: 20
                    height: 20
                    radius: 10
                    color: ffmpegRecorder.isRecording ? "#e74c3c" : "transparent"
                    border.color: "white"
                    border.width: 2

                    SequentialAnimation on opacity {
                        running: ffmpegRecorder.isRecording
                        loops: Animation.Infinite
                        NumberAnimation { from: 1.0; to: 0.3; duration: 500 }
                        NumberAnimation { from: 0.3; to: 1.0; duration: 500 }
                    }
                }

                Item { Layout.fillWidth: true }
            }

            // 调试信息
            Text {
                Layout.fillWidth: true
                text: "预览状态: " + (cameraActive ? "开启" : "关闭") +
                      " | 摄像头: " + getCameraStatusText(camera.cameraStatus) +
                      " | 录制器: " + (ffmpegRecorder.isRecording ? "录制中" : "空闲")
                color: "#666666"
                font.pixelSize: 10
            }
        }
    }

    // 录制计时器
    Timer {
        id: recordTimer
        interval: 1000
        repeat: true
        running: ffmpegRecorder.isRecording
        onTriggered: {
            recordingSeconds++
            updateTimerDisplay()
        }
    }

    // 启动录制定时器(延迟启动)
    Timer {
        id: startRecordingTimer
        property string savedPath: ""
        interval: 1000
        onTriggered: {
            console.log("延迟启动FFmpeg录制:", savedPath)
            ffmpegRecorder.startRecording(savedPath)
            recordingSeconds = 0
            updateTimerDisplay()
        }
    }

    // 重新启动预览定时器
    Timer {
        id: restartPreviewTimer
        interval: 500
        onTriggered: {
            console.log("重新启动摄像头预览")
            cameraActive = true
            if (camera.cameraStatus !== Camera.ActiveStatus) {
                camera.start()
            }
        }
    }

    // 连接FFmpegRecorder信号
    Connections {
        target: ffmpegRecorder

        function onStopCameraPreview() {
            console.log("停止摄像头预览")
            stopCameraPreview()
        }

        function onStartCameraPreview() {
            console.log("启动摄像头预览")
            restartPreviewTimer.start()
        }
    }

    // 初始化
    Component.onCompleted: {
        console.log("初始化摄像头...")
        if (QtMultimedia.availableCameras.length > 0) {
            camera.start()
            statusMessage = "摄像头就绪 - 点击开始录制"
        } else {
            statusMessage = "未检测到摄像头"
            startButton.enabled = false
        }
    }

    // 函数定义
    function startRecording() {
        var timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)
        var fileName = "ffmpeg_record_" + timestamp + ".mp4"
        var savePath = "C:/Users/Administrator/Desktop/" + fileName

        console.log("准备开始录制:", savePath)

        // 先停止摄像头预览
        stopCameraPreview()

        // 延迟启动FFmpeg录制
        startRecordingTimer.savedPath = savePath
        startRecordingTimer.start()
    }

    function stopRecording() {
        console.log("停止FFmpeg录制")
        ffmpegRecorder.stopRecording()
    }

    function stopCameraPreview() {
        console.log("停止摄像头预览")
        cameraActive = false
        camera.stop()
    }

    function updateTimerDisplay() {
        timerText.text = "录制时间: " + formatTime(recordingSeconds)
    }

    function getCameraStatusText(status) {
        switch(status) {
            case Camera.ActiveStatus: return "活动"
            case Camera.LoadingStatus: return "加载中"
            case Camera.StartingStatus: return "启动中"
            case Camera.StoppingStatus: return "停止中"
            case Camera.StandbyStatus: return "待机"
            case Camera.UnavailableStatus: return "不可用"
            default: return "未知"
        }
    }

    function formatTime(seconds) {
        var hours = Math.floor(seconds / 3600)
        var minutes = Math.floor((seconds % 3600) / 60)
        var secs = seconds % 60
        return (hours < 10 ? "0" + hours : hours) + ":" +
               (minutes < 10 ? "0" + minutes : minutes) + ":" +
               (secs < 10 ? "0" + secs : secs)
    }
}

main.cpp文件源码

复制代码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QProcess>
#include <QDebug>
#include <QDir>
#include <QTimer>

class FFmpegRecorder : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool isRecording READ isRecording NOTIFY isRecordingChanged)
    Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)

public:
    explicit FFmpegRecorder(QObject *parent = nullptr) : QObject(parent), m_isRecording(false) {}

    bool isRecording() const { return m_isRecording; }
    QString statusMessage() const { return m_statusMessage; }

    Q_INVOKABLE void startRecording(const QString &outputPath) {
        if (m_isRecording) {
            setStatusMessage("已经在录制中");
            return;
        }

        m_outputPath = outputPath;
        startFFmpegRecording(outputPath);
    }

    Q_INVOKABLE void stopRecording() {
        if (m_process && m_isRecording) {
            // 向FFmpeg发送q信号来优雅停止
            m_process->write("q");
            m_process->closeWriteChannel();
            setStatusMessage("正在停止录制...");
        }
    }

signals:
    void isRecordingChanged();
    void statusMessageChanged();

private:
    void startFFmpegRecording(const QString &outputPath) {
        QString ffmpegPath = "C:/ffmpeg/bin/ffmpeg.exe";

        QStringList arguments;
        arguments << "-f" << "dshow"
                  << "-i" << "video=Integrated Camera"
                  << "-t" << "300"  // 录制5分钟
                  << "-y"  // 覆盖已存在文件
                  << QDir::toNativeSeparators(outputPath);

        qDebug() << "启动FFmpeg录制...";

        m_process = new QProcess(this);

        connect(m_process, &QProcess::started, this, [this]() {
            m_isRecording = true;
            setStatusMessage("正在录制中...");
            emit isRecordingChanged();
        });

        connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                this, &FFmpegRecorder::onProcessFinished);

        connect(m_process, &QProcess::errorOccurred, this, &FFmpegRecorder::onProcessError);

        connect(m_process, &QProcess::readyReadStandardError, this, [this]() {
            QString errorOutput = m_process->readAllStandardError();
            qDebug() << "FFmpeg输出:" << errorOutput;
        });

        m_process->start(ffmpegPath, arguments);

        if (!m_process->waitForStarted(3000)) {
            setStatusMessage("启动FFmpeg失败: " + m_process->errorString());
            delete m_process;
            m_process = nullptr;
            m_isRecording = false;
            emit isRecordingChanged();
        }
    }

private slots:
    void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
        qDebug() << "FFmpeg进程结束,退出码:" << exitCode;

        m_isRecording = false;

        if (exitCode == 0) {
            setStatusMessage("录制完成: " + m_outputPath);
        } else {
            setStatusMessage("录制失败,退出码: " + QString::number(exitCode));
        }

        if (m_process) {
            m_process->deleteLater();
            m_process = nullptr;
        }

        emit isRecordingChanged();
    }

    void onProcessError(QProcess::ProcessError error) {
        qDebug() << "FFmpeg进程错误:" << error;

        m_isRecording = false;
        setStatusMessage("FFmpeg错误: " + QString::number(error));

        if (m_process) {
            m_process->deleteLater();
            m_process = nullptr;
        }
        emit isRecordingChanged();
    }

private:
    void setStatusMessage(const QString &message) {
        if (m_statusMessage != message) {
            m_statusMessage = message;
            emit statusMessageChanged();
        }
    }

private:
    QProcess *m_process = nullptr;
    bool m_isRecording;
    QString m_statusMessage;
    QString m_outputPath;
};

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

    qmlRegisterType<FFmpegRecorder>("FFmpeg", 1, 0, "FFmpegRecorder");

    QQmlApplicationEngine engine;

    FFmpegRecorder recorder;
    engine.rootContext()->setContextProperty("ffmpegRecorder", &recorder);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    engine.load(url);

    return app.exec();
}

#include "main.moc"

三、效果演示

相关推荐
小糖学代码9 小时前
Linux:11.线程概念与控制
linux·服务器·c语言·开发语言·c++
Larry_Yanan12 小时前
QML学习笔记(四十)QML的ApplicationWindow和StackView
c++·笔记·qt·学习·ui
Kratzdisteln14 小时前
【C语言】Dev-C++如何编译C语言程序?从安装到运行一步到位
c语言·c++
Dream it possible!15 小时前
LeetCode 面试经典 150_栈_有效的括号(52_20_C++_简单)(栈+哈希表)
c++·leetcode·面试··哈希表
kyle~15 小时前
C++--- override 关键字 强制编译器验证当前函数是否重写基类的虚函数
java·前端·c++
寻找华年的锦瑟15 小时前
Qt-配置文件(INI/JSON/XML)
开发语言·qt
HY小海16 小时前
【C++】AVL树实现
开发语言·数据结构·c++
仰泳的熊猫16 小时前
LeetCode:701. 二叉搜索树中的插入操作
数据结构·c++·算法·leetcode
gd632137416 小时前
银河麒麟 aarch64 linux 里面的 qt 怎么安装kit
linux·服务器·qt