You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt6 Android应用后台恢复后QVideoFrame::map崩溃问题求助

Qt6 Android应用后台恢复后调用QVideoFrame::map崩溃问题解决

问题背景

基于Qt6.5.0开发的Quick Android应用,使用Camera组件采集视频并通过全屏VideoOutput显示。点击"Take frame"按钮时,后端backend的setVideoSink方法会从QVideoSink获取QVideoFrame副本,调用map(只读模式)处理数据后unmap。应用前台运行正常,但进入后台再恢复后,调用map方法时崩溃。

崩溃日志

E libEGL  : call to OpenGL ES API with no current context (logged once per thread)
W libappCameraProcessor_arm64-v8a.so: Failed to compile shader:
W libappCameraProcessor_arm64-v8a.so: Source was:
W libappCameraProcessor_arm64-v8a.so: #version 100
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
W libappCameraProcessor_arm64-v8a.so: struct buf
W libappCameraProcessor_arm64-v8a.so: {
W libappCameraProcessor_arm64-v8a.so:     mat4 matrix;
W libappCameraProcessor_arm64-v8a.so:     mat4 colorMatrix;
W libappCameraProcessor_arm64-v8a.so:     float opacity;
W libappCameraProcessor_arm64-v8a.so:     float width;
W libappCameraProcessor_arm64-v8a.so:     float masteringWhite;
W libappCameraProcessor_arm64-v8a.so:     float maxLum;
W libappCameraProcessor_arm64-v8a.so: };
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
W libappCameraProcessor_arm64-v8a.so: uniform buf ubuf;
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
W libappCameraProcessor_arm64-v8a.so: varying vec2 texCoord;
W libappCameraProcessor_arm64-v8a.so: attribute vec2 vertexTexCoord;
W libappCameraProcessor_arm64-v8a.so: attribute vec4 vertexPosition;
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
W libappCameraProcessor_arm64-v8a.so: void main()
W libappCameraProcessor_arm64-v8a.so: {
W libappCameraProcessor_arm64-v8a.so:     texCoord = vertexTexCoord;
W libappCameraProcessor_arm64-v8a.so:     gl_Position = ubuf.matrix * vertexPosition;
W libappCameraProcessor_arm64-v8a.so: }
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
06-16 13:32:36.205 18373 18394 W libappCameraProcessor_arm64-v8a.so:
W CameraProcesso: 0xebadde09 skipped times: 0
F libc    : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x78 in tid 18394 (qtMainLoopThrea), pid 18373 (CameraProcessor)

原代码

Main.qml

import QtQuick.Window
import QtMultimedia
import QtQuick.Controls

import Backend

Window {
    width: 1920
    height: 1024
    visible: true

    Camera {
       id: userCamera
       focusMode: Camera.FocusModeAuto
    }

    CaptureSession {
       id: viewFinderCaptureSession
       videoOutput: viewfinder
       camera: userCamera
    }

    VideoOutput {
        id: viewfinder
        anchors.fill: parent
    }

    Backend{
        id: backend
        objectName: "backend"
    }

    Component.onCompleted: userCamera.start()

    Button {
        id: processButton
        onClicked: backend.videSink = viewfinder.videoSink

        width: 200
        height: 50
        text: "Take frame"

        x: 20
        y: 20
    }
}

backend.h

#include <QObject>
#include <QVideoSink>
#include <QVideoFrame>
#include <QVariant>

class backend : public QObject
{
    Q_OBJECT
public:
    backend(QObject* _parent = nullptr);

    Q_INVOKABLE void setVideoSink(QVideoSink* _videoSink);
    void disconnectSink();
};

backend.cpp

#include "backend.h"

#include <QDebug>

backend::backend(QObject* _parent):
    QObject(_parent)
{
    // noting to do...
}


void backend::setVideoSink(QVideoSink* _videoSink)
{
    auto myFrame = _videoSink->videoFrame();
    qDebug() << "map frame; is valid: " << myFrame.isValid() << "isMapped: " << myFrame.isMapped();
    myFrame.map(QVideoFrame::MapMode::ReadOnly);
    // TODO here comes some work ...
    myFrame.unmap();
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "backend.h"

int main(int argc, char *argv[])
{
    qmlRegisterType< backend >( "Backend", 1, 0, "Backend" );

    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
        &app, []() { QCoreApplication::exit(-1); },
        Qt::QueuedConnection);

    engine.loadFromModule("CameraProcessor", "Main");

    QObject::connect(&app, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState _state){
        qDebug() << "State changed to: " << _state;
    });

    return app.exec();
}

问题原因

  1. OpenGL上下文丢失:Android应用进入后台时,系统会回收OpenGL ES上下文;恢复后,QVideoFrame可能仍绑定已失效的旧上下文,调用map时触发无上下文的OpenGL调用,导致崩溃。
  2. Camera资源未正确重启:后台状态下Camera会被系统暂停,恢复后未重新初始化,导致获取的QVideoFrame无效或关联资源已释放。
  3. 未校验帧有效性:代码直接获取帧并执行map,未处理帧无效、上下文缺失的异常场景。

修复思路

  • 监听应用状态,后台时停止Camera并解除CaptureSession关联,恢复时重新绑定并启动Camera,确保资源重置。
  • 改用QVideoSink::videoFrameChanged信号获取最新帧,而非直接读取当前帧,避免拿到过期或无效帧。
  • 操作帧前校验帧有效性,针对GPU内存帧检查当前OpenGL上下文是否存在。

修复示例代码

1. 修改Main.qml,添加应用状态监听

import QtQuick.Window
import QtMultimedia
import QtQuick.Controls
import QtQuick 2.15

import Backend

Window {
    id: mainWindow
    width: 1920
    height: 1024
    visible: true

    property var app: Qt.application

    Camera {
       id: userCamera
       focusMode: Camera.FocusModeAuto
    }

    CaptureSession {
       id: viewFinderCaptureSession
       videoOutput: viewfinder
       camera: userCamera
    }

    VideoOutput {
        id: viewfinder
        anchors.fill: parent
    }

    Backend{
        id: backend
        objectName: "backend"
    }

    Component.onCompleted: userCamera.start()

    Button {
        id: processButton
        onClicked: backend.videoSink = viewfinder.videoSink

        width: 200
        height: 50
        text: "Take frame"

        x: 20
        y: 20
    }

    // 监听应用状态,处理Camera启停
    Connections {
        target: app
        function onApplicationStateChanged(state) {
            if (state === Qt.ApplicationSuspended) {
                userCamera.stop()
                viewFinderCaptureSession.camera = null
            } else if (state === Qt.ApplicationActive) {
                viewFinderCaptureSession.camera = userCamera
                userCamera.start()
            }
        }
    }
}

2. 修改backend.h,添加属性和信号槽

#include <QObject>
#include <QVideoSink>
#include <QVideoFrame>

class backend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVideoSink* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged)
public:
    explicit backend(QObject* parent = nullptr);

    QVideoSink* videoSink() const;
    void setVideoSink(QVideoSink* sink);

signals:
    void videoSinkChanged();

private slots:
    void onVideoFrameChanged(const QVideoFrame& frame);

private:
    QVideoSink* m_videoSink = nullptr;
};

3. 修改backend.cpp,添加帧校验和上下文检查

#include "backend.h"
#include <QDebug>
#include <QOpenGLContext>

backend::backend(QObject* parent): QObject(parent) {}

QVideoSink* backend::videoSink() const {
    return m_videoSink;
}

void backend::setVideoSink(QVideoSink* sink) {
    if (m_videoSink == sink) return;

    // 断开旧的信号连接
    if (m_videoSink) {
        disconnect(m_videoSink, &QVideoSink::videoFrameChanged, this, &backend::onVideoFrameChanged);
    }

    m_videoSink = sink;
    if (m_videoSink) {
        // 连接新的帧变化信号
        connect(m_videoSink, &QVideoSink::videoFrameChanged, this, &backend::onVideoFrameChanged);
    }
    emit videoSinkChanged();
}

void backend::onVideoFrameChanged(const QVideoFrame& frame) {
    // 校验帧有效性
    if (!frame.isValid()) {
        qDebug() << "Received invalid video frame";
        return;
    }

    // 针对OpenGL纹理帧,检查上下文是否存在
    if (frame.handleType() == QVideoFrame::OpenGLTextureHandle && !QOpenGLContext::currentContext()) {
        qDebug() << "No active OpenGL context, skip frame mapping";
        return;
    }

    QVideoFrame frameCopy = frame;
    if (frameCopy.map(QVideoFrame::ReadOnly)) {
        // 处理帧数据的逻辑写在这里
        qDebug() << "Frame mapped successfully: width=" << frameCopy.width() << ", height=" << frameCopy.height();
        
        frameCopy.unmap();
    } else {
        qDebug() << "Failed to map video frame";
    }
}

4. main.cpp无需修改,保持原状态即可

内容的提问来源于stack exchange,提问作者mtb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 04:53:12