Qt6 multimedia开发一个摄像头录像机

news2025/6/20 20:13:26

Qt 6 附加模块multimedia可用于多媒体的开发,今天使用它可以快速开发一个摄像头录像机。 毕业季用作本科毕业设计软件应该可以的。

支持的功能

  • 无边框窗口,并且支持拖拽,调整窗口大小
  • 切换摄像头
  • 配置摄像头原格式、分辨率、帧率、画面质量、
  • 抓图拍摄
  • 录像,支持声音

不支持硬件加速,纯CPU编解码,当前我没在qt文档中找到硬件加速的接口。
可使用ffmpeg实现GPU加速,将来再写文章。

效果展示

在这里插入图片描述

源码地址

https://gitee.com/noevilme/QtDemo/tree/master/CameraRecorder

源码分析

1. 外观设计

这里有人分享的一些无边框窗口的示例,可以参考修改
https://gitee.com/feiyangqingyun/QWidgetDemo/tree/master/ui/uidemo08
他这里使用的黑色,可以参考这个仓库下其他示例,更换一个外观风格。

皮肤资源

复制资源文件lightblue.css和lightblue目录到CameraRecorder\core_qss\qss
资源文件中添加刚才的两个资源。
在这里插入图片描述

修改外观

修改main.cpp中资源文件名

    //加载样式表
    QFile file(":/qss/lightblue.css");
    if (file.open(QFile::ReadOnly)) {
        QString qss = QLatin1String(file.readAll());
        QString paletteColor = qss.mid(20, 7);
        qApp->setPalette(QPalette(QColor(paletteColor)));
        qApp->setStyleSheet(qss);
        file.close();
    }

2. 无边框窗口的放大缩小拖拽

效果如下
在这里插入图片描述

由于setWindowFlags(Qt::FramelessWindowHint);// 设置窗口为无边框窗口,所以窗口是不支持边框放大与缩小拖拽的,因为已经没有边框了。
要支持拖拽,需要自己模拟实现,窗口边框附近变更鼠标符号,然后就计算坐标变化,再去变更窗口大小。

frmmain.h

private:
    //边距+可移动+可拉伸
    int padding;
    bool moveEnable;
    bool resizeEnable;

    //无边框窗体
    QWidget *widget;

    //鼠标是否按下+按下坐标+按下时窗体区域
    bool mousePressed;
    QPoint mousePoint;
    QRect mouseRect;

    //鼠标是否按下某个区域+按下区域的大小
    //依次为 左侧+右侧+上侧+下侧+左上侧+右上侧+左下侧+右下侧
    QList<bool> pressedArea;
    QList<QRect> pressedRect;

    void initResizeMembers();

frmmain.cpp 中需要去初始化这些成员变量,installEventFilter很重要,然后再去eventFilter响应鼠标事件,实现拉伸。

void frmMain::initResizeMembers() {
    //设置鼠标追踪为真,不然只会在鼠标按下时才会触发鼠标移动事件
    this->setMouseTracking(true);
    //设置悬停为真,必须设置这个,不然当父窗体里边还有子窗体全部遮挡了识别不到MouseMove,需要识别HoverMove
    this->setAttribute(Qt::WA_Hover, true);
    // setWindowFlags(Qt::FramelessWindowHint);// 设置窗口为无边框窗口
    // 只有安装了事件过滤器,才会进入到eventFilter,很重要!!
    installEventFilter(this);

    padding = 8;
    moveEnable = true;
    resizeEnable = true;
    widget = this;

    mousePressed = false;
    mousePoint = QPoint(0, 0);
    mouseRect = QRect(0, 0, 0, 0);

    for (int i = 0; i < 8; ++i) {
        pressedArea << false;
        pressedRect << QRect(0, 0, 0, 0);
    }
}


bool frmMain::eventFilter(QObject *watched, QEvent *event) {
    if (event->type() == QEvent::MouseButtonDblClick) {
        if (watched == ui->widgetTitle) {
            on_btnMenu_Max_clicked();
            return true;
        }
    }

    // qDebug() << "watched " << watched << ", event " << event->type();

    if (widget && watched == widget) {
        int type = event->type();
        if (type == QEvent::WindowStateChange) {
            //解决mac系统上无边框最小化失效的bug
#ifdef Q_OS_MACOS
            if (widget->windowState() & Qt::WindowMinimized) {
                isMin = true;
            } else {
                if (isMin) {
                    //设置无边框属性
                    widget->setWindowFlags(flags | Qt::FramelessWindowHint);
                    widget->setVisible(true);
                    isMin = false;
                }
            }
#endif
        } else if (type == QEvent::Resize) {
            //重新计算八个描点的区域,描点区域的作用还有就是计算鼠标坐标是否在某一个区域内
            int width = widget->width();
            int height = widget->height();

            //左侧描点区域
            pressedRect[0] = QRect(0, padding, padding, height - padding * 2);
            //右侧描点区域
            pressedRect[1] =
                QRect(width - padding, padding, padding, height - padding * 2);
            //上侧描点区域
            pressedRect[2] = QRect(padding, 0, width - padding * 2, padding);
            //下侧描点区域
            pressedRect[3] =
                QRect(padding, height - padding, width - padding * 2, padding);

            //左上角描点区域
            pressedRect[4] = QRect(0, 0, padding, padding);
            //右上角描点区域
            pressedRect[5] = QRect(width - padding, 0, padding, padding);
            //左下角描点区域
            pressedRect[6] = QRect(0, height - padding, padding, padding);
            //右下角描点区域
            pressedRect[7] =
                QRect(width - padding, height - padding, padding, padding);
        } else if (type == QEvent::HoverMove) {
            //设置对应鼠标形状,这个必须放在这里而不是下面,因为可以在鼠标没有按下的时候识别
            QHoverEvent *hoverEvent = (QHoverEvent *)event;
            QPoint point = hoverEvent->pos();
            if (resizeEnable) {
                if (pressedRect.at(0).contains(point)) {
                    widget->setCursor(Qt::SizeHorCursor);
                } else if (pressedRect.at(1).contains(point)) {
                    widget->setCursor(Qt::SizeHorCursor);
                } else if (pressedRect.at(2).contains(point)) {
                    widget->setCursor(Qt::SizeVerCursor);
                } else if (pressedRect.at(3).contains(point)) {
                    widget->setCursor(Qt::SizeVerCursor);
                } else if (pressedRect.at(4).contains(point)) {
                    widget->setCursor(Qt::SizeFDiagCursor);
                } else if (pressedRect.at(5).contains(point)) {
                    widget->setCursor(Qt::SizeBDiagCursor);
                } else if (pressedRect.at(6).contains(point)) {
                    widget->setCursor(Qt::SizeBDiagCursor);
                } else if (pressedRect.at(7).contains(point)) {
                    widget->setCursor(Qt::SizeFDiagCursor);
                } else {
                    widget->setCursor(Qt::ArrowCursor);
                }
            }

            //根据当前鼠标位置,计算XY轴移动了多少
            int offsetX = point.x() - mousePoint.x();
            int offsetY = point.y() - mousePoint.y();

            //根据按下处的位置判断是否是移动控件还是拉伸控件
            if (moveEnable && mousePressed) {
                widget->move(widget->x() + offsetX, widget->y() + offsetY);
            }

            if (resizeEnable) {
                int rectX = mouseRect.x();
                int rectY = mouseRect.y();
                int rectW = mouseRect.width();
                int rectH = mouseRect.height();

                if (pressedArea.at(0)) {
                    int resizeW = widget->width() - offsetX;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, rectY,
                                            resizeW, rectH);
                    }
                } else if (pressedArea.at(1)) {
                    widget->setGeometry(rectX, rectY, rectW + offsetX, rectH);
                } else if (pressedArea.at(2)) {
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(rectX, widget->y() + offsetY, rectW,
                                            resizeH);
                    }
                } else if (pressedArea.at(3)) {
                    widget->setGeometry(rectX, rectY, rectW, rectH + offsetY);
                } else if (pressedArea.at(4)) {
                    int resizeW = widget->width() - offsetX;
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, widget->y(),
                                            resizeW, resizeH);
                    }
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y() + offsetY,
                                            resizeW, resizeH);
                    }
                } else if (pressedArea.at(5)) {
                    int resizeW = rectW + offsetX;
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y() + offsetY,
                                            resizeW, resizeH);
                    }
                } else if (pressedArea.at(6)) {
                    int resizeW = widget->width() - offsetX;
                    int resizeH = rectH + offsetY;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, widget->y(),
                                            resizeW, resizeH);
                    }
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y(), resizeW,
                                            resizeH);
                    }
                } else if (pressedArea.at(7)) {
                    int resizeW = rectW + offsetX;
                    int resizeH = rectH + offsetY;
                    widget->setGeometry(widget->x(), widget->y(), resizeW,
                                        resizeH);
                }
            }
        } else if (type == QEvent::MouseButtonPress) {
            //记住鼠标按下的坐标+窗体区域
            QMouseEvent *mouseEvent = (QMouseEvent *)event;
            mousePoint = mouseEvent->pos();
            mouseRect = widget->geometry();

            //判断按下的手柄的区域位置
            if (pressedRect.at(0).contains(mousePoint)) {
                pressedArea[0] = true;
            } else if (pressedRect.at(1).contains(mousePoint)) {
                pressedArea[1] = true;
            } else if (pressedRect.at(2).contains(mousePoint)) {
                pressedArea[2] = true;
            } else if (pressedRect.at(3).contains(mousePoint)) {
                pressedArea[3] = true;
            } else if (pressedRect.at(4).contains(mousePoint)) {
                pressedArea[4] = true;
            } else if (pressedRect.at(5).contains(mousePoint)) {
                pressedArea[5] = true;
            } else if (pressedRect.at(6).contains(mousePoint)) {
                pressedArea[6] = true;
            } else if (pressedRect.at(7).contains(mousePoint)) {
                pressedArea[7] = true;
            } else {
                mousePressed = true;
            }
        } else if (type == QEvent::MouseMove) {
            //改成用HoverMove识别
        } else if (type == QEvent::MouseButtonRelease) {
            //恢复所有
            widget->setCursor(Qt::ArrowCursor);
            mousePressed = false;
            for (int i = 0; i < 8; ++i) {
                pressedArea[i] = false;
            }
        }
    }

    return QWidget::eventFilter(watched, event);
}

3. 控件设计

  • 摄像头列表,使用QTreeWidget
  • 配置与操作, 很多个小控件,比较常用的
  • 视频预览,使用拖放一个QWidget即可,后面需要提升到QVideoWidget
    在这里插入图片描述

在qmake配置文件form.pri中需要添加。CameraRecorder.pro添加也行。

QT += multimedia multimediawidgets

提升显示控件QWidget到QVideoWidget
在这里插入图片描述

4. 加载摄像头列表

在这里插入图片描述

  • 使用 QMediaDevices::videoInputs()获取摄像头信息QCameraDevice列表,
  • QMediaDevices::audioInputs() 获取音频设备QAudioDevice列表
    QCameraDevice和QAudioDevice都可以用QVariant::fromValue()直接保存到控件的Qt::UserRole中,打开设备的时候直接可用。
void frmMain::loadDevices() {
    ui->treeWidgetCamera->setHeaderHidden(true);
    QTreeWidgetItem *computer = new QTreeWidgetItem(ui->treeWidgetCamera);
    computer->setIcon(0, QIcon(":/image/computer.png"));
    computer->setText(0, "此电脑");
    computer->setExpanded(true);
    ui->treeWidgetCamera->addTopLevelItem(computer);

    auto cameras = QMediaDevices::videoInputs();
    for (const QCameraDevice &camera : cameras) {
        QTreeWidgetItem *item = new QTreeWidgetItem(computer);
        item->setIcon(0, QIcon(":/image/camera.png"));
        item->setText(0, camera.description());
        item->setData(0, Qt::UserRole, QVariant::fromValue(camera));

        qDebug() << camera.description() << ", id: " << camera.id();
        auto videoFormats = camera.videoFormats();
        for (auto &format : videoFormats) {
            qDebug() << " - resolution " << format.resolution()
                     << ", frame rate [" << format.minFrameRate() << ", "
                     << format.maxFrameRate() << "], format "
                     << format.pixelFormat();
        }
    }

    //    ui->comboBoxAudioDevice->addItem(tr("Default"), QVariant(QString()));
    for (auto device : QMediaDevices::audioInputs()) {
        auto name = device.description();
        ui->comboBoxAudioDevice->addItem(name, QVariant::fromValue(device));
    }

    ui->comboBoxQuality->addItem("很低", int(QImageCapture::VeryLowQuality));
    ui->comboBoxQuality->addItem("低", int(QImageCapture::LowQuality));
    ui->comboBoxQuality->addItem("正常", int(QImageCapture::NormalQuality));
    ui->comboBoxQuality->addItem("高", int(QImageCapture::HighQuality));
    ui->comboBoxQuality->addItem("很高", int(QImageCapture::VeryHighQuality));
    ui->comboBoxQuality->setCurrentIndex(2);
}

QCameraDevice::videoFormats()可以获取视频设备的分辨率、FPS、图像格式。
每种格式下支持的分辨率和FPS也是不一样的。YUYV数据量很大所以在高分辨率下FPS很低。
如果要高分辨率、最大FPS必须使用JPEG,这样USB带宽才够。

 - resolution  QSize(1920, 1080) , frame rate [ 30 ,  30 ], format  Format_NV12
 - resolution  QSize(1920, 1080) , frame rate [ 30 ,  30 ], format  Format_Jpeg
 - resolution  QSize(1920, 1080) , frame rate [ 25 ,  25 ], format  Format_NV12
 - resolution  QSize(1920, 1080) , frame rate [ 25 ,  25 ], format  Format_Jpeg
 - resolution  QSize(1280, 960) , frame rate [ 30 ,  30 ], format  Format_NV12
 - resolution  QSize(1280, 960) , frame rate [ 30 ,  30 ], format  Format_Jpeg
 - resolution  QSize(1280, 960) , frame rate [ 25 ,  25 ], format  Format_NV12
 - resolution  QSize(1280, 960) , frame rate [ 25 ,  25 ], format  Format_Jpeg
 - resolution  QSize(1280, 720) , frame rate [ 30 ,  30 ], format  Format_NV12
 - resolution  QSize(1280, 720) , frame rate [ 30 ,  30 ], format  Format_Jpeg
 - resolution  QSize(1280, 720) , frame rate [ 25 ,  25 ], format  Format_NV12
 - resolution  QSize(1280, 720) , frame rate [ 25 ,  25 ], format  Format_Jpeg
 - resolution  QSize(640, 480) , frame rate [ 30 ,  30 ], format  Format_NV12
 - resolution  QSize(640, 480) , frame rate [ 30 ,  30 ], format  Format_Jpeg
 - resolution  QSize(640, 480) , frame rate [ 25 ,  25 ], format  Format_NV12
 - resolution  QSize(640, 480) , frame rate [ 25 ,  25 ], format  Format_Jpeg
 - resolution  QSize(1920, 1080) , frame rate [ 5 ,  5 ], format  Format_YUYV
 - resolution  QSize(1280, 960) , frame rate [ 5 ,  5 ], format  Format_YUYV
 - resolution  QSize(1280, 720) , frame rate [ 10 ,  10 ], format  Format_YUYV
 - resolution  QSize(640, 480) , frame rate [ 30 ,  30 ], format  Format_YUYV
 - resolution  QSize(1920, 1080) , frame rate [ 5 ,  5 ], format  Format_NV12
 - resolution  QSize(1280, 960) , frame rate [ 10 ,  10 ], format  Format_NV12
 - resolution  QSize(1280, 720) , frame rate [ 15 ,  15 ], format  Format_NV12
 - resolution  QSize(640, 480) , frame rate [ 30 ,  30 ], format  Format_NV12

5. 打开摄像头及预览

双击树形控件的时候打开该节点的摄像头

void frmMain::on_treeWidgetCamera_itemDoubleClicked(QTreeWidgetItem *item,
                                                   int column) {

   // https://blog.csdn.net/u011442415/article/details/129370856

   auto cameraDevice = item->data(0, Qt::UserRole).value<QCameraDevice>();
   qDebug() << "激活摄像头" << cameraDevice.description() << ", id "
            << cameraDevice.id();

   // 加载摄像头支持的参数到控件
   loadCameraProperties(cameraDevice);
   setCamera(cameraDevice);

   auto videoFormats = cameraDevice.videoFormats();
   for (auto &format : videoFormats) {
       qDebug() << "resolution " << format.resolution() << ", frame rate ["
                << format.minFrameRate() << ", " << format.maxFrameRate()
                << "], format " << format.pixelFormat();
   }

   // https://doc.qt.io/qt-6/qcameraformat.html
   startCamera();
}

由于每个摄像头支持的格式、分辨率、FPS都不一样,所以得把这个信息保留下来,留到后面切换格式及分辨率的时候用。

void frmMain::loadCameraProperties(const QCameraDevice &camera) {
    ui->comboBoxPixFormat->clear();
    cameraFormats = camera.videoFormats();

    QSet<QVideoFrameFormat::PixelFormat> pixFormats;
    for (auto &format : cameraFormats) {
        if (!pixFormats.contains(format.pixelFormat())) {
            ui->comboBoxPixFormat->addItem(
                QVideoFrameFormat::pixelFormatToString(format.pixelFormat()),
                format.pixelFormat());
            pixFormats.insert(format.pixelFormat());
        }
    }

    ui->comboBoxPixFormat->setCurrentIndex(0);
}

然后就是用QCamera建立设备,并设置到QMediaCaptureSession中。
建立QMediaRecorder,QImageCapture, 图片截图可以设置成PNG,其他的可以自己改。

captureSession.setVideoOutput(ui->widgetVideo) 将输出显示设置到刚才提升的那个QVideoWidget控件。

void frmMain::setCamera(const QCameraDevice &cameraDevice) {
    curCamera.reset(new QCamera(cameraDevice));
    captureSession.setCamera(curCamera.data());

    if (!mediaRecorder) {
        mediaRecorder.reset(new QMediaRecorder);
        captureSession.setRecorder(mediaRecorder.data());
    }

    if (!imgCapture) {
        imgCapture.reset(new QImageCapture);
        imgCapture->setFileFormat(QImageCapture::PNG);

        captureSession.setImageCapture(imgCapture.get());
    }

    captureSession.setVideoOutput(ui->widgetVideo);
}

最后就可以通过curCamera->start() 打开摄像头了。

6. 截图

设置图像质量,imgCapture->captureToFile()就完成截图了,默认是在“图片”目录,可以使用绝对路径替换。

void frmMain::on_toolButtonCapture_clicked() {
    // https://doc.qt.io/qt-6.5/qimagecapture.html
    if (!curCamera) {
        QUIHelper::showMessageBoxError("没有打开摄像头");
        return;
    }

    auto quality =
        ui->comboBoxQuality->currentData().value<QImageCapture::Quality>();
    imgCapture->setQuality(quality);

    QDateTime currentDateTime = QDateTime::currentDateTime();
    QString fileName = currentDateTime.toString("yyyy-MM-dd_hhmmss");
    imgCapture->captureToFile(fileName);

    qDebug() << "已截图" << fileName;
}

7. 录音录像

QMediaFormat可以设置音频和视频编码格式,音频一般aac,视频H264即可。 (H265需要硬件支持,一般不行就会回退到H264)。
Quality, OutputLocation, EncodingMode设置完毕之后record()即可开启录像了。保存格式是mp4,默认在“视频”目录。

void frmMain::on_toolButtonRecord_clicked() {
    if (!curCamera) {
        QUIHelper::showMessageBoxError("没有打开摄像头");
        return;
    }
    if (!recording) {
        recording = true;

        if (ui->checkBoxAudio->isChecked()) {
            auto audioDevice =
                ui->comboBoxAudioDevice->currentData().value<QAudioDevice>();
            audioInput.reset(new QAudioInput(audioDevice));
            captureSession.setAudioInput(audioInput.get());
        } else {
            captureSession.setAudioInput(nullptr);
        }

        QMediaFormat format;
        format.setAudioCodec(QMediaFormat::AudioCodec::Unspecified); // aac
        format.setVideoCodec(QMediaFormat::VideoCodec::Unspecified); // h264
        mediaRecorder->setMediaFormat(format);

        // 值一样,控件共用
        auto quality =
            ui->comboBoxQuality->currentData().value<QMediaRecorder::Quality>();
        mediaRecorder->setQuality(quality);
        QDateTime currentDateTime = QDateTime::currentDateTime();
        QString fileName = currentDateTime.toString("yyyy-MM-dd_hhmmss");
        mediaRecorder->setOutputLocation(QUrl::fromLocalFile(fileName));
        mediaRecorder->setEncodingMode(QMediaRecorder::ConstantQualityEncoding);
        mediaRecorder->record();
        ui->toolButtonRecord->setText("停止");
    } else {
        recording = false;

        mediaRecorder->stop();
        ui->toolButtonRecord->setText("录像");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1584981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

opencv图像处理技术(形态学操作)

形态学&#xff08;Morphology&#xff09;是数学中研究形状、结构和变换的分支&#xff0c;而在图像处理中&#xff0c;形态学主要用于描述和分析图像中的形状和结构。形态学操作通常涉及基本的集合运算&#xff0c;如腐蚀、膨胀、开运算、闭运算等&#xff0c;以及与结构元素…

云原生__K8S

createrepo --update /var/localrepo/# 禁用 firewall 和 swap [rootmaster ~]# sed /swap/d -i /etc/fstab [rootmaster ~]# swapoff -a [rootmaster ~]# dnf remove -y firewalld-*[rootmaster ~]# vim /etc/hosts 192.168.1.30 harbor 192.168.1.50 master 192.168.1.…

互联网大厂ssp面经之路:计算机网络part1

1. 计算机网络的组成部分有哪些&#xff1f; a. 硬件设备&#xff1a;计算机网络由各种硬件设备组成&#xff0c;包括计算机、服务器、路由器、交换机、网卡等。这些设备通过物理连接&#xff08;如网线、光纤&#xff09;相互连接。 b. 协议&#xff1a;计算机网络中的通信需…

一个巧用委托解决的问题(C#)

个人觉得是委托应用的一个很好的例子&#xff0c;故做一下分享&#xff0c;希望能帮助到您&#xff0c;内容比较简单&#xff0c;大佬可以跳过。我是做桌面医疗软件开发的&#xff0c;前段时间在做一个需求。在签发检验项目医嘱时&#xff0c;调用第三方接口&#xff0c;然后带…

什么是生成式AI?有哪些特征类型

生成式AI是人类一种人工智能技术&#xff0c;可以生成各种类型的内容&#xff0c;包括文本、图像、音频和合成数据。那么什么是人工智能&#xff1f;人工智能和机器学习之间的区别是什么&#xff1f;有哪些技术特征&#xff1f; 人工智能是一门学科&#xff0c;是计算机科学的一…

漫途水产养殖水质智能监测方案,科技助力养殖业高效生产!

随着水产养殖业的蓬勃发展&#xff0c;水质和饲料等多重因素逐渐成为影响其持续健康发展的关键因素。由于传统养殖模式因监控和调节手段不足&#xff0c;往往造成养殖环境的恶化。需要通过智能化养殖&#xff0c;调控养殖环境&#xff0c;实现养殖的精细化管理模式&#xff0c;…

Python爬虫网络实践:去哪儿旅游数据爬取指南

Python爬虫网络实践&#xff1a;去哪儿旅游数据爬取指南 在这个博客中&#xff0c;我们将探索如何使用 Python 来进行网络数据抓取&#xff0c;并以抓取旅游数据为例进行演示。我们将通过一个简单的示例来说明如何利用 Python 中的常用库进行网页抓取&#xff0c;从而获取旅游…

Go语言开发工具Vscode配置

Go语言开发工具Vscode配置方法分享&#xff1a; 1.下载安装vscode https://code.visualstudio.com/ 2.汉化vscode 3.vscode中安装Go语言插件 源自&#xff1a;大地老师Golang语言beego入门实战视频教程下载地址

rsync 远程同步----------安全高效的异地备份方案

目录 一、rsync介绍 rsync和cp的区别 rsync和scp的区别 二、rsync同步方式 rsync备份的方式 三、配置rsync源服务器 ①本地复制 ②下行同步 ③上行同步 四、常用Rsync命令 五、配置源的两种表达方法 六、部署rsync下行同步 ①环境准备 ②配置rsync源服务器------…

[大模型]Qwen1.5-7B-Chat 接入 LangChain 搭建知识库助手

Qwen1.5-7B-Chat 接入 LangChain 搭建知识库助手 环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端…

String类(1)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&a…

45-基于Kubernetes的云原生架构设计

云原生简介 云原生包含的概念很多&#xff0c;对于一个应用开发者来说&#xff0c;主要关注点是如何开发应用&#xff0c;以及如何部署应用。会主要介绍应用层的云原生架构设计和系统资源层的云原生架构设计。 CNCF&#xff08;云原生计算基金会&#xff09;简介 CNCF&#x…

竞品数据的监测范围

常规的数据监测一般指的是价格监测&#xff0c;品牌对线上产品链接中的页面价、到手价进行监测&#xff0c;同时也可监测标题变化、销量变化、库存变化、优惠信息变化等&#xff0c;对于对够执行数据监测的系统来说&#xff0c;不管哪个品牌的数据都可做到以上维度的监测&#…

SOCKS代理概述

在网络技术的广阔领域中&#x1f310;&#xff0c;SOCKS代理是一个核心组件&#xff0c;它在提升在线隐私保护&#x1f6e1;️、实现匿名通信&#x1f3ad;以及突破网络访问限制&#x1f6ab;方面发挥着至关重要的作用。本文旨在深入探讨SOCKS代理的基础&#xff0c;包括其定义…

阿里云云效CI/CD配置

1.NODEJS项目流水线配置(vue举例) nodejs构建配置 官方教程 注意:下图的dist是vue项目打包目录名称,根据实际名称配置 # input your command here cnpm cache clean --force cnpm install cnpm run build 主机部署配置 rm -rf /home/vipcardmall/frontend/ mkdir -p /home/…

SV-7042V 40W网络有源音柱 智慧灯杆广播音柱

SV-7042V 40W网络有源音柱 一、描述 SV-7042V是深圳锐科达电子有限公司的一款壁挂式网络有源音柱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;其采用防水设计&#xff0c;功率40W。 SV-7042V作为网络广播播放系统的终…

Go——网络编程

一. 互联网协议介绍 网络基础——网络传输基本流程_网络传输过程-CSDN博客 应用层HTTP协议-CSDN博客 传输层UDP/TCP协议_udp报文提供的确认号用于接收方跟发送方确认-CSDN博客 网络层IP协议-CSDN博客 链路层以太网详解_以太网数据链路层-CSDN博客 二. Socket编程 Socket是…

智能运维场景 | 科技风险预警,能实现到什么程度?

[ 原作者&#xff1a;擎创夏洛克&#xff0c;本文略做了节选和改编 ] 每次一说到“风险预警”&#xff0c;就会有客户问我们能做怎样的风险预警。实际上在智能运维厂商来说&#xff0c;此风险非彼风险&#xff0c;不是能做银行的业务上的风险预警&#xff08;比如贷款风险等&a…

Day 22 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点

二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#…

2024年CISP认证详细报考流程和条件,建议收藏!

CISP&#xff08;Certified Information Security Professional&#xff0c;注册信息安全专业人员&#xff09;证书是由中国信息安全测评中心&#xff08;CNITSEC&#xff09;颁发的专业资质证书&#xff0c;旨在为信息安全领域培养和认证具备一定专业水平的安全人才。CISP证书…