#pragma once #include #include #include #include #include #include #include #include #include #include #include "v4l2worker.h" // VideoWidget shows the live V4L2 preview and can additionally record it // to an MP4 file. Recording works by piping raw BGRA frames into an // external `ffmpeg` process (via QProcess/stdin), which encodes them to // H.264/MP4 - this avoids reimplementing video encoding and just needs // ffmpeg to be installed and on PATH. class VideoWidget : public QWidget { Q_OBJECT public: explicit VideoWidget(QWidget *parent = nullptr); ~VideoWidget(); public: void shutdown(); // safe to call multiple times, called from MainWindow::closeEvent // Folder that saveAutoScreenshot() writes into. Empty = not configured // (saveAutoScreenshot() will report an error instead of silently // dropping the request). void setAutoScreenshotDir(const QString &dir) { m_autoScreenshotDir = dir; } public slots: void onNewFrame(const QImage &frame); void clearFrame(); // blanks the preview (used on ANSI clear-screen) // Saves whichever frame is currently in m_frame under `filename` inside // m_autoScreenshotDir. Triggered by a [SCREENSHOT] control tag from the // firmware - the point is to grab the frame that's already in flight // the instant the tag arrives (no dialog, no user reaction time), e.g. // to reliably catch a video frame showing a memory dump. void saveAutoScreenshot(const QString &filename); signals: // Emitted after every saveAutoScreenshot() attempt so the caller (the // main window) can surface a transient status message. Never blocks/ // pops a dialog itself, since this can fire in rapid succession from // firmware-driven triggers. void autoScreenshotResult(bool success, const QString &message); private slots: void onStartStop(); void onFreeze(bool frozen); void onScreenshot(); void onRecordToggled(bool checked); void refreshDeviceList(); protected: void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void setupUi(); void startCapture(); void stopCapture(); void updateScaled(); void startRecording(); void stopRecording(); void writeRecordingFrame(const QImage &frame); QThread *m_thread = nullptr; V4l2Worker *m_worker = nullptr; QImage m_frame; QImage m_scaled; bool m_frozen = false; bool m_running = false; bool m_shutdownDone = false; QComboBox *m_deviceCombo = nullptr; QPushButton *m_startBtn = nullptr; QPushButton *m_freezeBtn = nullptr; QPushButton *m_screenshotBtn = nullptr; QPushButton *m_recordBtn = nullptr; QLabel *m_infoLabel = nullptr; // Recording state. m_recordProcess is the running ffmpeg instance (or // nullptr when not recording); m_recordSize is the frame size the // recording was started with, so a mid-stream resolution change (which // ffmpeg's raw pipe can't handle) can be detected and the recording // stopped cleanly instead of corrupting the output file. QProcess *m_recordProcess = nullptr; bool m_recording = false; QSize m_recordSize; // Folder for saveAutoScreenshot() ([SCREENSHOT] tag). Set via // setAutoScreenshotDir(), persisted/restored by MainWindow. QString m_autoScreenshotDir; };