Qt 中使用 ffmpeg 获取采集卡数据录制视频

news2025/2/22 11:40:49

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

前言

之前做了一个功能,从采集卡获取数据然后录制成视频,结果发现录制的视频内存占用非常大,1分钟的视频大概有 800MB 内存。在帧率和分辨率已确定的情况下,只能通过调整比特率来减少内存占用,但是设置比特率在不同编码器和平台支持情况都有所不同,有些编码器甚至不支持直接设置比特率,所以博主想起了 ffmpeg 这个神器。

这里先介绍一下视频的相关参数:

在用户视角:
清晰度 = 比特率(码率) / 分辨率
流畅度 = 帧率

在开发者视角:
影响内存的:主要是分辨率
影响 CPU 的:码率和编码格式
影响 GPU 的:分辨率和编码格式
影响体积大小和带宽:码率

ffmpeg 库功能测试

首先在网上找到了 ffmpeg 库的 windows 安装包来做下测试
百度网盘下载链接:ffmpeg(windows安装包)
提取码:fkmn

下载完成之后可以直接使用 bin\ffmpeg.exe 在命令行做测试,把 ffmpeg\bin 路径添加到环境变量中

  1. 确认 ffmpeg 版本和配置:ffmpeg -version

  2. 列举所有设备:ffmpeg -list_devices true -f dshow -i dummy

对已录制的内存占用较大的视频进行压缩:

  1. 设置码率:ffmpeg -i input.mp4 -b:v 1000k output.mp4
  2. 设置分辨率:ffmpeg -i input.mp4 -s 640x360 output.mp4
  3. 设置帧率:ffmpeg -i input.mp4 -r 30 output.mp4
  4. 综合调整:ffmpeg -i input.mp4 -b:v 800k -s 640x360 -r 30 output.mp4

也可以用 python 跑脚本:

import subprocess
def compress_video(input_file, output_file, bitrate='1000k'):
    command = [
        'ffmpeg',
        '-i', input_file,
        '-b:v', bitrate,
        output_file
    ]
    try:
        subprocess.run(command, check=True)
        print(f"视频压缩成功,输出文件为: {output_file}")
    except subprocess.CalledProcessError as e:
        print(f"视频压缩失败: {e}")
        
# 使用示例
input_file = 'input.mp4'
output_file = 'output.mp4'
compress_video(input_file, output_file, bitrate='800k')

再测试一下直接打开设备录制视频

ffmpeg -f dshow -i video="@device_pnp_\\?\usb#vid_2b89&pid_5647&mi_00#7&223c07ce&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" -c:v libx264 -max_delay 10000 -b:v 1000k -bufsize 200k -r 30 output.mp4 -y

命令解释
-c:v libx264: 指定使用libx264编码器进行视频编码。
-max_delay 10000: 设置编码器的最大延迟为10000毫秒(10秒)。这有助于控制视频编码的缓冲延迟。
-b:v 1000k: 设置视频比特率为1000 kbps(千比特每秒)。
-bufsize 200k: 设置编码器的缓冲区大小为200 kbps。这个参数用于控制编码器在遇到高负载或低负载时的比特率变化。
-r 30: 设置帧率为30帧每秒。
output.mp4: 输出文件名。
-y: 覆盖输出文件,如果文件已存在。

测试结果为用 ffmpeg 压缩过的视频内存占用率非常小,码率越小内存就越小。但是对于客户来说他不会使用命令行去做压缩,所以最终方案还是直接使用 ffmpeg 库来录制视频

Qt 中集成 ffmpeg 库来录制视频

首先在网上下载编译完成的 ffmpeg 库,博主用的是 Qt 5.15.2 和 vs2019
百度网盘下载链接:ffmpeg(库文件)
提取码: cqwx

把库集成到 Qt 中

INCLUDEPATH += $$PWD/ffmpeg/include

LIBS += -L$$PWD/ffmpeg/lib/ -lavcodec
LIBS += -L$$PWD/ffmpeg/lib/ -lavdevice
LIBS += -L$$PWD/ffmpeg/lib/ -lavfilter
LIBS += -L$$PWD/ffmpeg/lib/ -lavformat
LIBS += -L$$PWD/ffmpeg/lib/ -lavutil
LIBS += -L$$PWD/ffmpeg/lib/ -lpostproc
LIBS += -L$$PWD/ffmpeg/lib/ -lswresample
LIBS += -L$$PWD/ffmpeg/lib/ -lswscale

实现拍照和录屏的功能

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavdevice/avdevice.h>
}

void showErrorInfo(int ret)
{
    static char errbuf[AV_ERROR_MAX_STRING_SIZE];
    memset(errbuf, 0, AV_ERROR_MAX_STRING_SIZE);
    av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
    qDebug() << "Error info:" << errbuf;
}

void videoRecord()
{
	avformat_network_init();
    avdevice_register_all();

    // 遍历所有设备类型
    // const AVInputFormat *iformat = nullptr;
    // while ((iformat = av_input_audio_device_next(iformat))) {
    //     qDebug() << "Audio input device: " << iformat->name << " - " << iformat->long_name;
    // }
    // iformat = nullptr;
    // while ((iformat = av_input_video_device_next(iformat))) {
    //     qDebug() << "Video input device: " << iformat->name << " - " << iformat->long_name;
    // }

    const AVInputFormat *iformat_dshow = av_find_input_format("dshow");
    if (!iformat_dshow)
    {
        qDebug() << "Could not find input format !";
        return;
    }

    // 用于存储设备列表的上下文
    AVDeviceInfoList *deviceList = nullptr;
    int ret = avdevice_list_input_sources(iformat_dshow, nullptr, nullptr, &deviceList);
    if (ret < 0) {
        qDebug() << "Could not list input sources !";
        showErrorInfo(ret);
        return;
    }

    // 设备名称
    std::string deviceName = "";

    // 遍历设备列表
    // qDebug() << "Available DirectShow devices:";
    for (int i = 0; i < deviceList->nb_devices; ++i)
    {
        AVDeviceInfo *device = deviceList->devices[i];
        // qDebug() << "Device" << i << ":" << device->device_description << "(" << device->device_name << ")";

        // 获取绿联采集卡的设备名称
        QString description = QString(device->device_description);
        if ( description.contains("UGREEN") ) {
            deviceName = "video=" + description.toStdString();
            break;
        }
    }

    // 释放设备列表
    avdevice_free_list_devices(&deviceList);

    //------------------------------

    // 创建 AVFormatContext
    AVFormatContext *inputFormatContext = avformat_alloc_context();
    if (!inputFormatContext) {
        qDebug() << "Could not allocate AVFormatContext !";
        return;
    }

    // 设置附加参数
    AVDictionary *options = nullptr;
    int framerate = 30;
    av_dict_set(&options, "rtbufsize", "100M", 0);  // 设置缓冲区大小
    av_dict_set(&options, "framerate", "30", 0);    // 设置帧率

    // 打开输入设备
    ret = avformat_open_input(&inputFormatContext, deviceName.c_str(), iformat_dshow, &options);
    if (ret < 0) {
        qDebug() << "Could not open input device !";
        showErrorInfo(ret);
        return;
    }

    // 打印输入设备的信息
    // av_dump_format(inputFormatContext, 0, deviceName.c_str(), 0);

    // 查找输入流信息
    ret = avformat_find_stream_info(inputFormatContext, nullptr);
    if (ret < 0) {
        qDebug() << "Could not find stream information !";
        showErrorInfo(ret);
        return;
    }

    // 查找视频流
    int videoStreamIndex = -1;
    for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++)
    {
        if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break;
        }
    }
    if (videoStreamIndex == -1) {
        qDebug() << "Could not find video stream !";
        return;
    }

    // 获取视频流参数
    AVCodecParameters *inputCodecParameters = inputFormatContext->streams[videoStreamIndex]->codecpar;

    // 查找输入解码器
    const AVCodec *inputCodec = avcodec_find_decoder(inputCodecParameters->codec_id);
    if (!inputCodec) {
        qDebug() << "Could not find codec !";
        return ;
    }

    // 创建输入解码器上下文
    AVCodecContext *inputCodecContext = avcodec_alloc_context3(inputCodec);
    if (!inputCodecContext) {
        qDebug() << "Could not allocate codec context !";
        return;
    }

    // 将视频流参数复制到输入解码器上下文
    ret = avcodec_parameters_to_context(inputCodecContext, inputCodecParameters);
    if (ret < 0) {
        qDebug() << "Could not copy codec parameters to context !";
        showErrorInfo(ret);
        return;
    }

    // 打开输入解码器
    ret = avcodec_open2(inputCodecContext, inputCodec, nullptr);
    if (ret < 0) {
        qDebug() << "Could not open codec !";
        showErrorInfo(ret);
        return;
    }

    //------------------------------

    // 分配帧和数据包
    AVFrame *frame = av_frame_alloc();
    AVPacket *packet = av_packet_alloc();
    if (!frame || !packet) {
        qDebug() << "Could not alloc frame and packet !";
        return;
    }

    // 分配图像转换上下文
    SwsContext *swsContext = sws_getContext(inputCodecContext->width, inputCodecContext->height, inputCodecContext->pix_fmt,
                                            inputCodecContext->width, inputCodecContext->height, AV_PIX_FMT_RGB24,
                                            SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!swsContext) {
        qDebug() << "Could not initialize SwsContext !";
        return;
    }

    // 分配 RGB 帧
    AVFrame *rgbFrame = av_frame_alloc();
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, inputCodecContext->width, inputCodecContext->height, 1);
    uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,
                         inputCodecContext->width, inputCodecContext->height, 1);

    // 读取帧数据(保存10张图片)
    int frameCount = 0;
    while (av_read_frame(inputFormatContext, packet) >= 0 && frameCount < 11)
    {
        // 确保帧的数据类型和解码器的数据类型一致
        if (packet->stream_index == videoStreamIndex)
        {
            // 发送数据包到解码器
            ret = avcodec_send_packet(inputCodecContext, packet);
            if (ret < 0) {
                qDebug() << "Error sending packet to decoder !";
                showErrorInfo(ret);
                continue;
            }

            // 接收解码后的帧
            while (avcodec_receive_frame(inputCodecContext, frame) == 0)
            {
                // 舍弃第一帧
                if ( frameCount == 0 ) {
                    frameCount++;
                    continue;
                }

                // 转换图像格式
                sws_scale(swsContext, frame->data, frame->linesize, 0, inputCodecContext->height,
                          rgbFrame->data, rgbFrame->linesize);

                // 创建 QImage
                QImage image(rgbFrame->data[0], inputCodecContext->width, inputCodecContext->height, QImage::Format_RGB888);

                // 保存图像
                QString fileName = QString("frame_%1.jpg").arg(frameCount++);
                if (!image.save(fileName)) {
                    qDebug() << "Error saving image:" << fileName;
                } else {
                    qDebug() << "Image saved:" << fileName;
                }
            }
        }
        av_packet_unref(packet);
    }

    //------------------------------

    // 输出格式上下文
    AVFormatContext *outputFormatContext = nullptr;
    const char *outputFileName = "output.mp4";

    // 创建输出格式上下文
    ret = avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputFileName);
    if (!outputFormatContext) {
        qDebug() << "Could not create output context !";
        showErrorInfo(ret);
        return;
    }

    // 查找输出编码器
    const AVCodec *outputCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!outputCodec) {
        qDebug() << "Could not find output codec !";
        return;
    }

    // 创建输出流
    AVStream *outputStream = avformat_new_stream(outputFormatContext, outputCodec);
    if (!outputStream) {
        qDebug() << "Could not create output stream !";
        return;
    }

    // 打开输出编码器上下文
    AVCodecContext *outputCodecContext = avcodec_alloc_context3(outputCodec);
    if (!outputCodecContext) {
        qDebug() << "Could not allocate output codec context !";
        return;
    }

    // 设置输出编码器参数
    outputCodecContext->codec_id = outputCodec->id;
    outputCodecContext->codec_type = AVMEDIA_TYPE_VIDEO;
    outputCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
    outputCodecContext->width = 1920;                                       // 设置分辨率
    outputCodecContext->height = 1080;
    outputCodecContext->time_base = {1, framerate};                         // 设置帧率
    outputCodecContext->framerate = {framerate, 1};
    outputCodecContext->bit_rate = 2000000;                                 // 设置比特率
    outputCodecContext->rc_buffer_size = 2 * outputCodecContext->bit_rate;  // 设置缓冲区大小为码率的两倍
    outputCodecContext->gop_size = 10;                                      // 设置关键帧间隔
    outputCodecContext->max_b_frames = 1;

    if (outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
        outputCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开输出编码器
    ret = avcodec_open2(outputCodecContext, outputCodec, nullptr);
    if (ret < 0) {
        qDebug() << "Could not open output codec !";
        showErrorInfo(ret);
        return;
    }

    // 复制输出编码器参数到输出流
    avcodec_parameters_from_context(outputStream->codecpar, outputCodecContext);
    outputStream->time_base = outputCodecContext->time_base;

    //------------------------------

    // 打开输出文件
    if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&outputFormatContext->pb, outputFileName, AVIO_FLAG_WRITE);
        if (ret < 0) {
            qDebug() << "Could not open output file !";
            showErrorInfo(ret);
            return;
        }
    }

    // 写入文件头
    ret = avformat_write_header(outputFormatContext, nullptr);
    if (ret < 0) {
        qDebug() << "Could not write header !";
        showErrorInfo(ret);
        return;
    }

    // 分配帧和数据包
    AVFrame *inputFrame = av_frame_alloc();
    AVPacket *inputPacket = av_packet_alloc();
    AVFrame *outputFrame = av_frame_alloc();
    AVPacket *outputPacket = av_packet_alloc();
    if (!inputFrame || !inputPacket || !outputFrame || !outputPacket) {
        qDebug() << "Could not alloc frame and packet !";
        return;
    }

    // 分配图像转换上下文
    SwsContext *swsContext2 = sws_getContext(inputCodecContext->width, inputCodecContext->height, inputCodecContext->pix_fmt,
                                             outputCodecContext->width, outputCodecContext->height, outputCodecContext->pix_fmt,
                                             SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!swsContext2) {
        qDebug() << "Could not initialize SwsContext !";
        return;
    }

    // 分配输出帧数据
    outputFrame->format = AV_PIX_FMT_YUV420P;
    outputFrame->width = inputCodecContext->width;
    outputFrame->height = inputCodecContext->height;
    ret = av_frame_get_buffer(outputFrame, 0);
    if (ret < 0) {
        qDebug() << "Could not get frame buffer !";
        showErrorInfo(ret);
        return;
    }

    // 录屏60秒,帧率30,保存1800帧数据
    int frameCount2 = 0;
    while (av_read_frame(inputFormatContext, inputPacket) >= 0 && frameCount2 < 1800)
    {
        // 确保帧的数据类型和解码器的数据类型一致
        if (inputPacket->stream_index == videoStreamIndex)
        {
            // 发送输入数据包到解码器
            ret = avcodec_send_packet(inputCodecContext, inputPacket);
            if (ret < 0) {
                qDebug() << "Error sending packet to decoder !";
                showErrorInfo(ret);
                continue;
            }

            // 接收解码后的帧
            while (avcodec_receive_frame(inputCodecContext, inputFrame) == 0)
            {
                // 转换图像格式
                sws_scale(swsContext2, inputFrame->data, inputFrame->linesize, 0, inputCodecContext->height,
                          outputFrame->data, outputFrame->linesize);

                outputFrame->pts = frameCount2++;
                qDebug() << "frameCount2: " << frameCount2;

                // 发送输出帧到输出编码器
                ret = avcodec_send_frame(outputCodecContext, outputFrame);
                if (ret < 0) {
                    qDebug() << "Error sending frame to encoder !";
                    showErrorInfo(ret);
                    continue;
                }

                // 接收编码后的数据包
                while (avcodec_receive_packet(outputCodecContext, outputPacket) == 0)
                {
                    av_packet_rescale_ts(outputPacket, outputCodecContext->time_base, outputStream->time_base);
                    outputPacket->stream_index = outputStream->index;

                    // 写入数据包到输出文件
                    ret = av_interleaved_write_frame(outputFormatContext, outputPacket);
                    if (ret < 0) {
                        qDebug() << "Error writing packet to output file";
                        showErrorInfo(ret);
                        continue;
                    }

                    av_packet_unref(outputPacket);
                }
            }
        }
        av_packet_unref(inputPacket);
    }

    // 刷新编码器
    avcodec_send_frame(outputCodecContext, nullptr);
    while (avcodec_receive_packet(outputCodecContext, outputPacket) == 0)
    {
        av_packet_rescale_ts(outputPacket, outputCodecContext->time_base, outputStream->time_base);
        outputPacket->stream_index = outputStream->index;

        ret = av_interleaved_write_frame(outputFormatContext, outputPacket);
        if (ret < 0) {
            qDebug() << "Error writing packet to output file";
            showErrorInfo(ret);
        }
        av_packet_unref(outputPacket);
    }

    // 写入文件尾
    av_write_trailer(outputFormatContext);

    // 释放资源
    av_frame_free(&inputFrame);
    av_frame_free(&outputFrame);
    av_packet_free(&inputPacket);
    av_packet_free(&outputPacket);
    sws_freeContext(swsContext2);
    avcodec_free_context(&outputCodecContext);
    avformat_free_context(outputFormatContext);

    if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&outputFormatContext->pb);
    }

    av_free(buffer);
    av_frame_free(&rgbFrame);
    av_frame_free(&frame);
    av_packet_free(&packet);
    sws_freeContext(swsContext);
    avcodec_free_context(&inputCodecContext);
    avformat_close_input(&inputFormatContext);
    avformat_free_context(inputFormatContext);
    av_dict_free(&options);

    avformat_network_deinit();
}

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

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

相关文章

文心一言大模型的“三级跳”:从收费到免费再到开源,一场AI生态的重构实验

2025年2月&#xff0c;百度文心大模型接连抛出两枚“重磅炸弹”&#xff1a;4月1日起全面免费&#xff0c;6月30日正式开源文心大模型4.5系列。这一系列动作不仅颠覆了李彦宏此前坚持的“闭源优势论”13&#xff0c;更标志着中国AI大模型竞争进入了一个全新的阶段——从技术壁垒…

Uniapp 从入门到精通:基础篇 - 搭建开发环境

Uniapp 从入门到精通:基础篇 - 搭建开发环境 前言一、Uniapp 简介1.1 什么是 Uniapp1.2 Uniapp 的优势二、搭建开发环境前的准备2.1 安装 Node.js2.2 安装 HBuilderX三、创建第一个 Uniapp 项目3.1 打开 HBuilderX 并创建项目3.2 项目结构介绍3.3 运行项目四、配置项目4.1 配置…

CSDN文章质量分查询系统【赠python爬虫、提分攻略】

CSDN文章质量分查询系统 https://www.csdn.net/qc 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链接-----> https://www.csdn.net/qc <------点击链接 点击链接-----> CSDN文章质量分查询系统 <------点击链接 点击链…

GPT-SoVITS更新V3 win整合包

GPT-SoVITS 是由社区开发者联合打造的开源语音生成框架&#xff0c;其创新性地融合了GPT语言模型与SoVITS&#xff08;Singing Voice Inference and Timbre Synthesis&#xff09;语音合成技术&#xff0c;实现了仅需5秒语音样本即可生成高保真目标音色的突破。该项目凭借其开箱…

CSS三大特性——继承、优先级与层叠

1. 层叠性 概念&#xff1a;如果发生了样式冲突&#xff0c;那就会根据一定的规则&#xff08;选择器优先级&#xff09;&#xff0c;进行样式的层叠&#xff08;覆 盖&#xff09;。 什么是样式冲突&#xff1f; ——— 元素的同一个样式名&#xff0c;被设置了不同的值&…

敏捷开发06:用户故事估算方法介绍

估算介绍 在以前开发 IT 软件时&#xff0c;使用较多的衡量软件开发工作量的单位是&#xff1a;小时、人天 或 人月。它是预估开发时间。比如&#xff1a;这个功能张三一个人开发需要 3 天时间完成。 这种 “人天” 估算只是 “理想人天” 的估算&#xff0c;有时与实际开发完…

环境变量与本地变量

目录 本地变量的创建 环境变量VS本地变量 认识完了环境变量我们来认识一下本地变量。 本地变量的创建 我们如果直接env是看不到本地变量的&#xff0c;因为本地变量和环境变量都具有独立性&#xff0c;环境变量是系统提供的具有全局属性的变量&#xff0c;都存在bash进程的…

Visual Studio中打开多个项目

1) 找到解决方案窗口 2) 右键添加→ 选择现有项目 3) 选择.vcxproj文件打开即可

广西壮族自治区园区投促中心党委书记陶德文率团到访深兰科技

2月16日&#xff0c;广西壮族自治区园区投促中心党委书记、主任&#xff0c;自治区园区办党组成员陶德文率团来到深兰科技集团上海总部考察调研&#xff0c;并与深兰科技集团创始人、董事长陈海波等集团管理层座谈交流&#xff0c;双方围绕深兰科技人工智能项目落地广西的相关事…

1005 K 次取反后最大化的数组和(贪心)

文章目录 题目[](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/)算法原理源码总结 题目 如上图&#xff0c;k是取反的次数&#xff0c;在数组【4&#xff0c;-1,3】中&#xff0c;当k 1&#xff0c;把-2取反为2&#xff0c;和为9&#xff1b;在数组…

Softing线上研讨会 | 自研还是购买——用于自动化产品的工业以太网

| 线上研讨会时间&#xff1a;2025年1月27日 16:00~16:30 / 23:00~23:30 基于以太网的通信在工业自动化网络中的重要性日益增加。设备制造商正面临着一大挑战——如何快速、有效且经济地将工业以太网协议集成到其产品中。其中的关键问题包括&#xff1a;是否只需集成单一的工…

SpringBoot整合Redis和Redision锁

参考文章 1.Redis 1.导入依赖 <!--Redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.c…

数仓搭建(hive):DM搭建(数据集市层)

DM层 数据集市层 &#xff08;Data Mart&#xff09; 粒度上卷&#xff08;Roll-up&#xff09;: 指的是沿着维度层次向上聚合汇总数据&#xff0c;从细粒度到粗粒度观察数据的操作。 示例 数仓的上一层DWS的是按日汇总 DM层基于DWS层主题日宽表上卷统计出按年,月,周的数…

用 Python 实现 DeepSeek R1 本地化部署

DeepSeek R1 以其出色的表现脱颖而出&#xff0c;不少朋友想将其本地化部署&#xff0c;网上基于 ollama 的部署方式有很多&#xff0c;但今天我要带你领略一种全新的方法 —— 使用 Python 实现 DeepSeek R1 本地化部署&#xff0c;让你轻松掌握&#xff0c;打造属于自己的 AI…

GitHub基本操作及Git简单命令

GitHub简介 GitHub就是一个远程仓库&#xff0c;远程仓库可以理解为就是一个可以保存自己代码的地方&#xff0c;在实际开发当中一个项目往往是有多个人来共同协作开发完成的&#xff0c;那么就需要一个统一代码保存的地方&#xff0c;而GitHub就是起到一个共享和汇总代码的作…

AI工作流+专业知识库+系统API的全流程任务自动化

我有点悲观&#xff0c;甚至很沮丧&#xff0c;因为AI留给普通人的机会不多了&#xff0c;这既是人类之间权力的斗争&#xff0c;也是硅基生命和碳基生命的斗争。AI自动化是无法避免的趋势&#xff0c;如果人类不能平权&#xff0c;那就只能跪下接受审判。 通过整合AI工作流、专…

本地文件共享——HFS

目录 1.介绍&#xff1a; 2.下载&#xff1a; 3.开始使用&#xff1a; 1.介绍&#xff1a; HFS&#xff08;HTTP File Server&#xff09;是一款轻量级的本地文件共享软件&#xff0c;主要用于快速搭建一个基于网页的临时文件服务器&#xff0c;支持通过浏览器直接上传或下载…

第十二届先进制造技术与材料工程国际学术会议 (AMTME 2025)

重要信息 大会官网&#xff1a;www.amtme.org&#xff08;了解会议&#xff0c;投稿等&#xff09; 大会时间&#xff1a;2025年3月21-23日 大会地点&#xff1a;中国-广州 简介 2025年第十二届先进制造技术与材料工程 (AMTME 2025) 定于2025年3月21-23日在中国广州隆重举…

【Alertmanager】alertmanager告警系统原理剖析与应用实战,应有尽有非常全面

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

C++之虚函数、虚函数表

C 虚函数、虚函数表详解与实践 C中虚函数是实现多态的重要技术&#xff0c;接下来将从汇编、以及gdb调试运行方面下手全面了解虚函数、虚函数表、以及虚函数调用。 原理初认识 一个由虚函数的类将会有一个虚函数表&#xff0c;且所有该类的实例化对象共享一个虚函数表。虚函…