当前位置: 首页 > news >正文

视频采集程序

项目结构:

image

VideoCaptureApp.pro

QT       += core gui multimedia multimediawidgetsgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11win32 {
LIBS += -L$$PWD/lib/SDL2/lib/x64 \-L$$PWD/lib/ffmpeg-4.2.1/lib/x64 \-lSDL2 \-lavcodec \-lavdevice \-lavfilter \-lavformat \-lavutil \-lswresample \-lswscaleINCLUDEPATH += src \lib/SDL2/include \lib/ffmpeg-4.2.1/include_x64
}# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0SOURCES += \ffmpegrecorder.cpp \main.cpp \mainwindow.cppHEADERS += \ffmpegrecorder.h \mainwindow.hFORMS += \mainwindow.ui# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

ffmpegrecorder.h

#ifndef FFMPEGRECORDER_H
#define FFMPEGRECORDER_H
#include <QThread>
#include <QMutex>// FFmpeg 头文件
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}// 分辨率结构体
struct Resolution {int width;int height;QString name;Resolution(int w = 0, int h = 0, const QString &n = ""): width(w), height(h), name(n) {}
};// 注册 Resolution 类型到 Qt 的元对象系统
Q_DECLARE_METATYPE(Resolution)// 录制线程类
class FFmpegRecorder : public QThread
{Q_OBJECTpublic:explicit FFmpegRecorder(QObject *parent = nullptr);~FFmpegRecorder();bool initialize(const QString &outputFile, int width, int height, int fps);void stopRecording();void addFrame(const QImage &image);signals:void errorOccurred(const QString &error);void recordingStatusChanged(const QString &status);protected:void run() override;private:bool setupOutput(const QString &outputFile);bool encodeFrame(const QImage &image);void cleanup();// FFmpeg 相关变量AVFormatContext *outputFormatContext;AVCodecContext *videoCodecContext;AVStream *videoStream;SwsContext *swsContext;AVFrame *frame;AVPacket *packet;int64_t frameCount;// 录制参数QString outputFilename;int frameWidth;int frameHeight;int frameRate;// 状态控制QMutex mutex;bool recording;bool initialized;// 帧队列QList<QImage> frameQueue;QMutex queueMutex;
};#endif // FFMPEGRECORDER_H

ffmpegrecorder.cpp

#include "ffmpegrecorder.h"
#include <QDebug>
#include <QFileDialog>// FFmpeg 错误处理辅助函数
QString ffmpegErrorString(int errnum)
{char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(errnum, errbuf, sizeof(errbuf));return QString(errbuf);
}// FFmpegRecorder 实现
FFmpegRecorder::FFmpegRecorder(QObject *parent): QThread(parent), outputFormatContext(nullptr),videoCodecContext(nullptr), videoStream(nullptr), swsContext(nullptr),frame(nullptr), packet(nullptr), frameCount(0),frameWidth(640), frameHeight(480), frameRate(30),recording(false), initialized(false)
{// 注册所有 FFmpeg 组件avdevice_register_all();avformat_network_init();
}FFmpegRecorder::~FFmpegRecorder()
{stopRecording();if (isRunning()) {wait(3000); // 等待线程结束,最多3秒}cleanup();
}bool FFmpegRecorder::initialize(const QString &outputFile,int width, int height, int fps)
{frameWidth = width;frameHeight = height;frameRate = fps;outputFilename = outputFile;// 只设置输出(文件),不设置输入(摄像头)if (!setupOutput(outputFile)) {emit errorOccurred("无法初始化输出文件");return false;}initialized = true;return true;
}bool FFmpegRecorder::setupOutput(const QString &outputFile)
{int ret;// 创建输出格式上下文ret = avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputFile.toUtf8().constData());if (ret < 0) {emit errorOccurred(QString("无法创建输出上下文: %1").arg(ffmpegErrorString(ret)));return false;}// 查找 H.264 编码器AVCodec *videoCodec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!videoCodec) {emit errorOccurred("找不到 H.264 编码器");return false;}// 创建视频流videoStream = avformat_new_stream(outputFormatContext, videoCodec);if (!videoStream) {emit errorOccurred("无法创建视频流");return false;}// 配置编码器上下文videoCodecContext = avcodec_alloc_context3(videoCodec);if (!videoCodecContext) {emit errorOccurred("无法分配编码器上下文");return false;}videoCodecContext->codec_id = AV_CODEC_ID_H264;videoCodecContext->codec_type = AVMEDIA_TYPE_VIDEO;videoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;videoCodecContext->width = frameWidth;videoCodecContext->height = frameHeight;videoCodecContext->time_base = {1, frameRate};videoCodecContext->framerate = {frameRate, 1};videoCodecContext->gop_size = 12;videoCodecContext->max_b_frames = 1;videoCodecContext->bit_rate = 400000; // 400kbps// 根据分辨率调整比特率if (frameWidth >= 1920) {videoCodecContext->bit_rate = 2000000; // 1080p 使用 2Mbps} else if (frameWidth >= 1280) {videoCodecContext->bit_rate = 1000000; // 720p 使用 1Mbps} else {videoCodecContext->bit_rate = 400000;  // 其他分辨率使用 400kbps}// 设置编码器预设if (outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {videoCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;}AVDictionary *codecOptions = nullptr;av_dict_set(&codecOptions, "preset", "medium", 0);av_dict_set(&codecOptions, "crf", "23", 0);// 打开编码器ret = avcodec_open2(videoCodecContext, videoCodec, &codecOptions);if (ret < 0) {emit errorOccurred(QString("无法打开视频编码器: %1").arg(ffmpegErrorString(ret)));return false;}// 复制编码器参数到流ret = avcodec_parameters_from_context(videoStream->codecpar, videoCodecContext);if (ret < 0) {emit errorOccurred(QString("无法复制编码器参数: %1").arg(ffmpegErrorString(ret)));return false;}// 打开输出文件if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {ret = avio_open(&outputFormatContext->pb, outputFile.toUtf8().constData(), AVIO_FLAG_WRITE);if (ret < 0) {emit errorOccurred(QString("无法打开输出文件: %1").arg(ffmpegErrorString(ret)));return false;}}// 写入文件头ret = avformat_write_header(outputFormatContext, nullptr);if (ret < 0) {emit errorOccurred(QString("无法写入文件头: %1").arg(ffmpegErrorString(ret)));return false;}// 分配帧和包frame = av_frame_alloc();packet = av_packet_alloc();if (!frame || !packet) {emit errorOccurred("无法分配帧或包");return false;}frame->format = videoCodecContext->pix_fmt;frame->width = videoCodecContext->width;frame->height = videoCodecContext->height;ret = av_frame_get_buffer(frame, 32);if (ret < 0) {emit errorOccurred(QString("无法分配帧缓冲区: %1").arg(ffmpegErrorString(ret)));return false;}// 创建图像转换上下文swsContext = sws_getContext(frameWidth, frameHeight, AV_PIX_FMT_RGB24,frameWidth, frameHeight, AV_PIX_FMT_YUV420P,SWS_BILINEAR, nullptr, nullptr, nullptr);if (!swsContext) {emit errorOccurred("无法创建图像转换上下文");return false;}return true;
}void FFmpegRecorder::stopRecording()
{QMutexLocker locker(&mutex);recording = false;
}void FFmpegRecorder::addFrame(const QImage &image)
{QMutexLocker locker(&queueMutex);if (frameQueue.size() < 30) { // 限制队列大小,防止内存溢出// 调整图像大小以匹配编码器设置QImage scaledImage = image.scaled(frameWidth, frameHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);frameQueue.append(scaledImage.copy()); // 复制图像,避免原始图像被修改}
}void FFmpegRecorder::run()
{if (!initialized) {emit errorOccurred("录制器未初始化");return;}recording = true;frameCount = 0;emit recordingStatusChanged("开始录制");// 计算帧间隔(毫秒)int frameInterval = 1000 / frameRate;while (recording) {auto startTime = std::chrono::steady_clock::now();// 从队列获取帧QImage currentFrame;{QMutexLocker locker(&queueMutex);if (!frameQueue.isEmpty()) {currentFrame = frameQueue.takeFirst();}}if (!currentFrame.isNull()) {if (!encodeFrame(currentFrame)) {emit errorOccurred("编码帧失败");break;}frameCount++;// 更新状态if (frameCount % 30 == 0) { // 每30帧更新一次状态emit recordingStatusChanged(QString("正在录制... 已录制 %1 帧").arg(frameCount));}}// 控制帧率auto endTime = std::chrono::steady_clock::now();auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();if (elapsed < frameInterval) {msleep(frameInterval - elapsed);}}// 刷新编码器encodeFrame(QImage());// 写入文件尾if (outputFormatContext) {av_write_trailer(outputFormatContext);}emit recordingStatusChanged(QString("录制完成,共 %1 帧").arg(frameCount));
}bool FFmpegRecorder::encodeFrame(const QImage &image)
{int ret;if (!image.isNull()) {// 转换图像格式为 RGB888QImage rgbImage = image.convertToFormat(QImage::Format_RGB888);// 准备源数据const uint8_t *srcData[1] = { rgbImage.bits() };int srcLinesize[1] = { rgbImage.bytesPerLine() };// 转换图像格式sws_scale(swsContext, srcData, srcLinesize, 0, frameHeight,frame->data, frame->linesize);frame->pts = frameCount;}// 发送帧到编码器ret = avcodec_send_frame(videoCodecContext, image.isNull() ? nullptr : frame);if (ret < 0) {qDebug() << "发送帧到编码器失败:" << ffmpegErrorString(ret);return false;}// 接收编码后的包while (ret >= 0) {ret = avcodec_receive_packet(videoCodecContext, packet);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {qDebug() << "接收编码包失败:" << ffmpegErrorString(ret);return false;}// 调整时间戳av_packet_rescale_ts(packet, videoCodecContext->time_base, videoStream->time_base);packet->stream_index = videoStream->index;// 写入包ret = av_interleaved_write_frame(outputFormatContext, packet);av_packet_unref(packet);if (ret < 0) {qDebug() << "写入包失败:" << ffmpegErrorString(ret);return false;}}return true;
}void FFmpegRecorder::cleanup()
{if (swsContext) {sws_freeContext(swsContext);swsContext = nullptr;}if (frame) {av_frame_free(&frame);frame = nullptr;}if (packet) {av_packet_free(&packet);packet = nullptr;}if (videoCodecContext) {avcodec_free_context(&videoCodecContext);videoCodecContext = nullptr;}if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {avio_closep(&outputFormatContext->pb);}if (outputFormatContext) {avformat_free_context(outputFormatContext);outputFormatContext = nullptr;}// 清空帧队列{QMutexLocker locker(&queueMutex);frameQueue.clear();}initialized = false;
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QCamera>
#include <QCameraViewfinder>
#include <QCameraInfo>
#include <QVBoxLayout>
#include <QPushButton>
#include <QComboBox>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QDateTime>
#include <QLabel>
#include <QTimer>
#include <chrono>
#include "ffmpegrecorder.h"namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();bool checkCameraAvailability();private slots:void startCamera();void stopCamera();void startRecording();void stopRecording();void takeScreenshot();void cameraError(QCamera::Error error);void updateRecordTime();void captureFrame();void onRecordingError(const QString &error);void onRecordingStatusChanged(const QString &status);void onResolutionChanged(int index);private:Ui::MainWindow *ui;void setupUI();void populateCameras();void populateResolutions();QString getOutputFileName();Resolution getSelectedResolution();QCamera *camera;QCameraViewfinder *viewfinder;QTimer *recordTimer;QTimer *frameCaptureTimer;qint64 recordDuration;// FFmpeg 录制器FFmpegRecorder *recorder;bool isRecording;// 分辨率列表QList<Resolution> resolutions;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMetaType>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QDateTime>
#include <QImageWriter>// MainWindow 实现
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow),camera(nullptr), viewfinder(nullptr),  // 明确初始化为 nullptrrecordDuration(0), recorder(nullptr), isRecording(false)
{qDebug() << "=== MainWindow Constructor ===";// 在构造函数开始时注册 Resolution 类型qRegisterMetaType<Resolution>();ui->setupUi(this);qDebug() << "UI setup completed";// 初始化定时器recordTimer = new QTimer(this);frameCaptureTimer = new QTimer(this);qDebug() << "Timers created";setupUI();qDebug() << "UI setup function called";// 检查摄像头可用性qDebug() << "=== Camera Availability Check ===";checkCameraAvailability();populateCameras();qDebug() << "Cameras populated";populateResolutions();qDebug() << "Resolutions populated";// 初始化 FFmpeg 录制器recorder = new FFmpegRecorder(this);connect(recorder, &FFmpegRecorder::errorOccurred, this, &MainWindow::onRecordingError);connect(recorder, &FFmpegRecorder::recordingStatusChanged, this, &MainWindow::onRecordingStatusChanged);qDebug() << "FFmpeg recorder initialized";qDebug() << "=== MainWindow Initialized Successfully ===";
}MainWindow::~MainWindow()
{stopRecording();stopCamera();delete ui;
}bool MainWindow::checkCameraAvailability()
{QList<QCameraInfo> cameras = QCameraInfo::availableCameras();if (cameras.isEmpty()) {qDebug() << "No cameras found";return false;}qDebug() << "Available cameras:";for (const QCameraInfo &cameraInfo : cameras) {qDebug() << " -" << cameraInfo.description() << "Device:" << cameraInfo.deviceName();// 检查摄像头是否可用QCamera testCamera(cameraInfo);if (testCamera.error() != QCamera::NoError) {qDebug() << "   Camera error:" << testCamera.error();} else {qDebug() << "   Camera seems available";}}return true;
}void MainWindow::setupUI()
{// UI 元素已经在 .ui 文件中定义,这里进行连接connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::startCamera);connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stopCamera);connect(ui->recordButton, &QPushButton::clicked, this, &MainWindow::startRecording);connect(ui->stopRecordButton, &QPushButton::clicked, this, &MainWindow::stopRecording);connect(ui->screenshotButton, &QPushButton::clicked, this, &MainWindow::takeScreenshot);connect(ui->resolutionComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),this, &MainWindow::onResolutionChanged);// 初始化按钮状态ui->stopButton->setEnabled(false);ui->recordButton->setEnabled(false);ui->stopRecordButton->setEnabled(false);// 创建录制计时器recordTimer = new QTimer(this);connect(recordTimer, &QTimer::timeout, this, &MainWindow::updateRecordTime);// 创建帧捕获定时器frameCaptureTimer = new QTimer(this);connect(frameCaptureTimer, &QTimer::timeout, this, &MainWindow::captureFrame);setWindowTitle("视频采集与录制程序 (FFmpeg API)");resize(800, 600);
}void MainWindow::populateCameras()
{ui->cameraComboBox->clear();QList<QCameraInfo> cameras = QCameraInfo::availableCameras();if (cameras.isEmpty()) {ui->cameraComboBox->addItem("未找到摄像头");ui->startButton->setEnabled(false);return;}for (const QCameraInfo &cameraInfo : cameras) {ui->cameraComboBox->addItem(cameraInfo.description(), cameraInfo.deviceName());}
}void MainWindow::populateResolutions()
{// 清空分辨率列表resolutions.clear();ui->resolutionComboBox->clear();// 添加常见分辨率选项resolutions.append(Resolution(1920, 1080, "1080p (1920x1080)"));resolutions.append(Resolution(1280, 720, "720p (1280x720)"));resolutions.append(Resolution(1024, 768, "XGA (1024x768)"));resolutions.append(Resolution(800, 600, "SVGA (800x600)"));resolutions.append(Resolution(640, 480, "VGA (640x480)"));resolutions.append(Resolution(320, 240, "QVGA (320x240)"));// 添加到下拉框for (const Resolution &res : resolutions) {ui->resolutionComboBox->addItem(res.name, QVariant::fromValue(res));}// 默认选择 720pui->resolutionComboBox->setCurrentIndex(1);
}void MainWindow::startCamera()
{qDebug() << "=== Starting Camera ===";try {if (ui->cameraComboBox->currentData().isNull()) {QMessageBox::warning(this, "警告", "没有可用的摄像头");return;}// 先停止当前摄像头stopCamera();QString deviceName = ui->cameraComboBox->currentData().toString();QString cameraDescription = ui->cameraComboBox->currentText();qDebug() << "Using camera:" << cameraDescription << "Device:" << deviceName;// 创建摄像头对象camera = new QCamera(deviceName.toUtf8(), this);if (!camera) {throw std::runtime_error("Failed to allocate camera object");}qDebug() << "Camera object created";// 连接错误信号connect(camera, QOverload<QCamera::Error>::of(&QCamera::error),this, &MainWindow::cameraError);// 创建视图viewfinder = new QCameraViewfinder(this);if (!viewfinder) {throw std::runtime_error("Failed to allocate viewfinder");}viewfinder->setMinimumSize(640, 480);qDebug() << "Viewfinder created";// 简化布局操作 - 直接添加到最后ui->verticalLayout->addWidget(viewfinder);qDebug() << "Viewfinder added to layout";// 设置视图并启动摄像头camera->setViewfinder(viewfinder);qDebug() << "Viewfinder set for camera";camera->start();qDebug() << "Camera start command issued";// 更新按钮状态ui->startButton->setEnabled(false);ui->stopButton->setEnabled(true);ui->recordButton->setEnabled(true);ui->statusLabel->setText("摄像头已启动");qDebug() << "=== Camera Started Successfully ===";} catch (const std::exception& e) {qDebug() << "Exception in startCamera:" << e.what();QMessageBox::critical(this, "错误", QString("启动摄像头失败: %1").arg(e.what()));stopCamera();}
}void MainWindow::stopCamera()
{qDebug() << "=== Stopping Camera ===";// 先停止录制stopRecording();qDebug() << "Stopping camera components...";// 先停止摄像头if (camera) {qDebug() << "Stopping camera object...";try {// 先断开所有信号连接disconnect(camera, nullptr, this, nullptr);// 停止摄像头if (camera->state() != QCamera::UnloadedState) {camera->stop();qDebug() << "Camera stopped";}// 使用 deleteLater 而不是直接 deletecamera->deleteLater();camera = nullptr;qDebug() << "Camera scheduled for deletion";} catch (const std::exception& e) {qDebug() << "Error stopping camera:" << e.what();}}// 处理视图if (viewfinder) {qDebug() << "Removing viewfinder...";try {// 先从布局中移除if (ui->verticalLayout) {// 安全地从布局中移除部件QLayoutItem* item = nullptr;for (int i = 0; i < ui->verticalLayout->count(); ++i) {item = ui->verticalLayout->itemAt(i);if (item && item->widget() == viewfinder) {ui->verticalLayout->removeItem(item);delete item;qDebug() << "Viewfinder removed from layout";break;}}}// 隐藏并删除视图viewfinder->hide();viewfinder->deleteLater();viewfinder = nullptr;qDebug() << "Viewfinder scheduled for deletion";} catch (const std::exception& e) {qDebug() << "Error removing viewfinder:" << e.what();}}// 更新按钮状态ui->startButton->setEnabled(true);ui->stopButton->setEnabled(false);ui->recordButton->setEnabled(false);ui->stopRecordButton->setEnabled(false);ui->statusLabel->setText("摄像头已停止");qDebug() << "=== Camera Stopped Successfully ===";
}void MainWindow::startRecording()
{if (!camera) {QMessageBox::warning(this, "警告", "请先启动摄像头");return;}if (isRecording) {QMessageBox::information(this, "提示", "已经在录制中");return;}QString fileName = getOutputFileName();if (fileName.isEmpty()) {return;}// 获取选中的分辨率Resolution selectedRes = getSelectedResolution();// 初始化录制器if (!recorder->initialize(fileName, selectedRes.width, selectedRes.height, 30)) {QMessageBox::critical(this, "错误", "初始化录制器失败");return;}// 开始录制recorder->start();isRecording = true;ui->recordButton->setEnabled(false);ui->stopRecordButton->setEnabled(true);recordDuration = 0;recordTimer->start(1000);frameCaptureTimer->start(33); // 30fps ≈ 33ms per frameui->statusLabel->setText(QString("正在录制... 分辨率: %1").arg(selectedRes.name));qDebug() << "Recording started with resolution:" << selectedRes.name;
}void MainWindow::stopRecording()
{if (isRecording && recorder) {recorder->stopRecording();frameCaptureTimer->stop();// 等待录制线程结束if (recorder->isRunning()) {recorder->wait(3000);}recordTimer->stop();ui->recordButton->setEnabled(true);ui->stopRecordButton->setEnabled(false);isRecording = false;qDebug() << "Recording stopped";}
}void MainWindow::captureFrame()
{// 捕获当前帧并添加到录制队列if (isRecording && viewfinder) {QImage frame = viewfinder->grab().toImage();if (!frame.isNull()) {recorder->addFrame(frame);}}
}void MainWindow::takeScreenshot()
{if (!camera || !viewfinder) {QMessageBox::warning(this, "警告", "请先启动摄像头");return;}QString picturesDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);if (picturesDir.isEmpty()) {picturesDir = QDir::currentPath();}QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");QString defaultName = QString("%1/screenshot_%2.png").arg(picturesDir).arg(timestamp);QString fileName = QFileDialog::getSaveFileName(this, "保存截图", defaultName,"图片文件 (*.png *.jpg *.bmp)");if (fileName.isEmpty()) {return;}QPixmap screenshot = viewfinder->grab();if (screenshot.isNull()) {QMessageBox::warning(this, "警告", "截图失败");return;}QString format = "PNG";if (fileName.endsWith(".jpg", Qt::CaseInsensitive) || fileName.endsWith(".jpeg", Qt::CaseInsensitive)) {format = "JPG";} else if (fileName.endsWith(".bmp", Qt::CaseInsensitive)) {format = "BMP";}if (screenshot.save(fileName, format.toUtf8().constData())) {ui->statusLabel->setText(QString("截图已保存: %1").arg(QFileInfo(fileName).fileName()));qDebug() << "Screenshot saved to:" << fileName;} else {QMessageBox::warning(this, "警告", "保存截图失败");}
}void MainWindow::cameraError(QCamera::Error error)
{qDebug() << "Camera error:" << error;QMessageBox::critical(this, "摄像头错误", QString("摄像头发生错误: %1").arg(error));stopCamera();
}void MainWindow::updateRecordTime()
{recordDuration++;Resolution selectedRes = getSelectedResolution();ui->statusLabel->setText(QString("正在录制... 时长: %1秒 分辨率: %2").arg(recordDuration).arg(selectedRes.name));
}void MainWindow::onRecordingError(const QString &error)
{QMessageBox::critical(this, "录制错误", error);stopRecording();
}void MainWindow::onRecordingStatusChanged(const QString &status)
{ui->statusLabel->setText(status);qDebug() << "Recording status:" << status;
}void MainWindow::onResolutionChanged(int index)
{if (index >= 0 && index < resolutions.size()) {Resolution res = resolutions.at(index);qDebug() << "Resolution changed to:" << res.name;// 如果正在录制,更新状态显示if (isRecording) {ui->statusLabel->setText(QString("正在录制... 分辨率: %1").arg(res.name));}}
}Resolution MainWindow::getSelectedResolution()
{int index = ui->resolutionComboBox->currentIndex();if (index >= 0 && index < resolutions.size()) {// 使用 QVariant::value<Resolution>() 获取存储的 Resolution 对象return ui->resolutionComboBox->itemData(index).value<Resolution>();}// 默认返回 720preturn Resolution(1280, 720, "720p (1280x720)");
}QString MainWindow::getOutputFileName()
{QString videosDir = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);if (videosDir.isEmpty()) {videosDir = QDir::currentPath();}QDir dir(videosDir);if (!dir.exists()) {dir.mkpath(".");}QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");Resolution res = getSelectedResolution();QString resolutionStr = QString("%1x%2").arg(res.width).arg(res.height);QString defaultName = QString("%1/video_%2_%3.mp4").arg(videosDir).arg(timestamp).arg(resolutionStr);QString fileName = QFileDialog::getSaveFileName(this, "保存视频", defaultName,"MP4 文件 (*.mp4);;所有文件 (*)");if (!fileName.isEmpty() && !fileName.endsWith(".mp4", Qt::CaseInsensitive)) {fileName += ".mp4";}return fileName;
}

main.cpp

#include "mainwindow.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;w.show();return a.exec();
}

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"><class>MainWindow</class><widget class="QMainWindow" name="MainWindow"><property name="geometry"><rect><x>0</x><y>0</y><width>800</width><height>600</height></rect></property><property name="windowTitle"><string>视频采集与录制程序</string></property><widget class="QWidget" name="centralWidget"><layout class="QVBoxLayout" name="verticalLayout"><item><widget class="QLabel" name="label"><property name="sizePolicy"><sizepolicy hsizetype="Fixed" vsizetype="Fixed"><horstretch>0</horstretch><verstretch>0</verstretch></sizepolicy></property><property name="text"><string>摄像头:</string></property></widget></item><item><widget class="QComboBox" name="cameraComboBox"/></item><item><widget class="QLabel" name="label_2"><property name="sizePolicy"><sizepolicy hsizetype="Fixed" vsizetype="Fixed"><horstretch>0</horstretch><verstretch>0</verstretch></sizepolicy></property><property name="text"><string>分辨率:</string></property></widget></item><item><widget class="QComboBox" name="resolutionComboBox"><property name="toolTip"><string>选择录制分辨率</string></property></widget></item><item><layout class="QHBoxLayout" name="horizontalLayout"><item><widget class="QPushButton" name="startButton"><property name="text"><string>开始采集</string></property></widget></item><item><widget class="QPushButton" name="stopButton"><property name="text"><string>停止采集</string></property></widget></item><item><widget class="QPushButton" name="recordButton"><property name="text"><string>开始录制</string></property></widget></item><item><widget class="QPushButton" name="stopRecordButton"><property name="text"><string>停止录制</string></property></widget></item><item><widget class="QPushButton" name="screenshotButton"><property name="text"><string>截图</string></property></widget></item></layout></item><item><widget class="QLabel" name="statusLabel"><property name="text"><string>就绪</string></property></widget></item></layout></widget></widget><resources/><connections/>
</ui>

 

主要特点

  1. 直接使用 FFmpeg API:不通过命令行调用,直接使用 libavcodec、libavformat 等库

  2. 多线程录制:在单独的线程中进行视频编码和写入,避免阻塞 UI

  3. 帧队列机制:使用队列管理待编码的帧,平衡生产和消费速度

  4. 实时预览:使用 Qt 的 QCameraViewfinder 进行实时预览

  5. 高质量编码:使用 H.264 编码,可配置编码参数

  6. 错误处理:完善的错误处理和状态反馈

使用说明

  1. 编译运行:确保 FFmpeg 4.2.1 库正确链接

  2. 选择摄像头:从下拉框选择要使用的摄像头

  3. 开始预览:点击"开始采集"启动摄像头预览

  4. 开始录制:点击"开始录制"选择保存位置并开始录制

  5. 停止录制:点击"停止录制"结束录制过程

  6. 截图功能:随时可以保存当前预览画面

这个实现提供了专业级的视频录制功能,直接使用 FFmpeg API 进行硬件加速编码,性能更好,控制更精细。

image

 

http://www.hskmm.com/?act=detail&tid=26695

相关文章:

  • java作业2
  • 关于PPT的课后作业
  • RK 系列 GPU 驱动检查方法
  • 咕乡
  • Linux随记(十八) - 详解
  • week2课后作业
  • Java 语言程序设计(第二讲 方法)动手动脑与课后实验问题整理文档 - 20243867孙堃2405
  • 算法第一章
  • mac打开app提示文件损坏解决方案
  • QBXT2025S刷题 Day7题
  • 无需重新训练即可更新语音识别词汇
  • 深入解析:vscode中无法使用npm node
  • 第一次算法作业
  • AI元人文:新的评价与启示
  • Ai元人文:岐金兰回应
  • Why is English commonly used in scientific literature?
  • 第二次课程
  • 考研系列—操作系统:冲刺笔记(1-3章) - 指南
  • 智能照明系统厂家最新推荐榜:智慧光控与节能方案口碑之选
  • 2025工业网线优质厂家最新推荐榜:品质卓越与技术领先之选
  • 上海殡葬一条龙服务最新推荐:专业关怀与人性化服务口碑之选
  • 中空扳手实力厂家最新推荐榜:专业制造与耐用品质深度解析
  • 驾驭“人造太阳”:用 AI 来解锁聚变核能
  • sg.Multiline 和 sg.Output 有什么区别?怎么看起来一样?
  • 中科微GNSS卫星定位产品
  • 算法设计与分析第一章作业
  • Syncfusion重构Essential Studio套件,为开发者提供更灵活选择
  • vmware workstation17pro安装vmtools
  • 2025 年逸发粘接认证推荐:依托德系标准与全链条服务,打造粘接及复材技术解决方案优质选择
  • ZR 2025 十一集训 Day 8