#pragma once #include #include #include #include #include #include #include #include #include #include #include "v4l2worker.h" // A uniform, icon-only push button used for the video toolbar (start/stop, // freeze, screenshot, record, fullscreen, audio, ...). All toolbar buttons // share the same fixed square size and font so the row lines up neatly // instead of every button sizing itself to its own label. QPushButton *makeToolbarIconButton(const QString &symbol, const QString &tooltip, QWidget *parent); // A borderless top-level window that mirrors the live video frame at full // screen size. Opened from VideoWidget's "⛶" button; closes itself on // Escape or a double-click anywhere on it. class FullscreenVideoView : public QWidget { Q_OBJECT public: explicit FullscreenVideoView(QWidget *parent = nullptr); void setFrame(const QImage &frame); signals: // Emitted from closeEvent() so VideoWidget can drop its pointer before // the WA_DeleteOnClose deletion actually happens. void closed(); protected: void paintEvent(QPaintEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void closeEvent(QCloseEvent *event) override; private: QImage m_frame; }; // 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; } // Folders the manual "Screenshot"/"Record" buttons' save dialogs // default to. Set by MainWindow from persisted settings on startup; // VideoWidget reports back via lastScreenshotDirChanged()/ // lastVideoDirChanged() whenever the user picks a new folder so // MainWindow can persist it for next time. void setLastScreenshotDir(const QString &dir) { m_lastScreenshotDir = dir; } void setLastVideoDir(const QString &dir) { m_lastVideoDir = 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); // Emitted whenever the user successfully saves via the manual // "Screenshot"/"Record" buttons, so MainWindow can persist the folder // for next time (see setLastScreenshotDir()/setLastVideoDir()). void lastScreenshotDirChanged(const QString &dir); void lastVideoDirChanged(const QString &dir); private slots: void onStartStop(); void onFreeze(bool frozen); void onScreenshot(); void onRecordToggled(bool checked); void onFullscreenToggle(); void onAudioToggled(bool checked); void refreshDeviceList(); void refreshAudioDevices(); 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); void startAudio(); void stopAudio(); static QStringList enumerateAudioDevices(); 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; QPushButton *m_fullscreenBtn = nullptr; QLabel *m_infoLabel = nullptr; // Audio passthrough: plays the grabber's ALSA capture device (e.g. the // "USB Audio" card that accompanies a /dev/videoN HDMI/USB grabber) // straight to the default output, the same way the HDMI viewer does it. // Implemented by shelling out to `ffplay -f alsa -i ` with // -nodisp (audio only, no extra window) - same approach as recording, // just piping the other direction (device -> speakers instead of // frames -> file). QComboBox *m_audioCombo = nullptr; QPushButton *m_audioBtn = nullptr; QProcess *m_audioProcess = nullptr; bool m_audioActive = false; // Fullscreen overlay window (nullptr when not open). Owns itself via // Qt::WA_DeleteOnClose; VideoWidget just tracks whether one currently // exists and forwards frames to it while it's open. FullscreenVideoView *m_fullscreenView = 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; // Folders the manual Screenshot/Record save dialogs default to (see // setLastScreenshotDir()/setLastVideoDir() above). QString m_lastScreenshotDir; QString m_lastVideoDir; };