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

Qt调用FFmpeg库实时播放UDP组播视频流

基于以下参考链接,通过改进实现实时播放UDP组播视频流

https://blog.csdn.net/u012532263/article/details/102736700

源码在windows(qt-opensource-windows-x86-5.12.9.exe)、ubuntu20.04.6(x64)(qt-opensource-linux-x64-5.12.12.run)、以及针对arm64的ubuntu20.04.6(x64)交叉编译环境下编译成功(QT5.12.8, 5.15.13), 可执行程序在windows,ubuntu(x64)、arm64上均可运行。

工程代码见:

https://download.csdn.net/download/daqinzl/90315016

主要代码
videoplayer.cpp

#include "videoplayer.h"
#include <QDebug>
extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
}

#include <stdio.h>
#include<iostream>
using namespace std;
VideoPlayer::VideoPlayer()
{

}

VideoPlayer::~VideoPlayer()
{

}

void VideoPlayer::startPlay()
{
    ///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程
    this->start();

}

void VideoPlayer::run()
{
    /*
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame;
    AVFrame *pFrameRGB;
    AVPacket *packet;
    uint8_t *out_buffer;

    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    avformat_network_init();
    av_register_all();


    //Allocate an AVFormatContext.
    pFormatCtx = avformat_alloc_context();

    // ffmpeg取rtsp流时av_read_frame阻塞的解决办法 设置参数优化
    AVDictionary* avdic = NULL;
    //rtsp
    //av_dict_set(&avdic, "buffer_size", "102400", 0); //设置缓存大小,1080p可将值调大
    //av_dict_set(&avdic, "rtsp_transport", "udp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp

    //rtmp
    //av_dict_set(&avdic, "buffer_size", "8192000", 0); //设置缓存大小,1080p可将值调大
    //av_dict_set(&avdic, "rtsp_transport", "tcp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp

    //udp
    av_dict_set(&avdic, "buffer_size", "8192000", 0); //设置缓存大小,1080p可将值调大
    av_dict_set(&avdic, "rtsp_transport", "udp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp
    //av_dict_set(&avdic, "fflags", "nobuffer", 0); // 设置实时选项
    //av_dict_set(&avdic, "flags", "low_delay", 0); // 设置低延迟选项
    av_dict_set(&avdic, "max_interleave_delta", "40000", 0);


    //av_dict_set(&avdic, "stimeout", "2000000", 0);   //设置超时断开连接时间,单位微秒
    //av_dict_set(&avdic, "max_delay", "500000", 0);   //设置最大时延

    ///rtsp地址,可根据实际情况修改
    //char url[]="rtsp://admin:admin@192.168.1.18:554/h264/ch1/main/av_stream";
    //char url[]="rtsp://192.168.17.112/test2.264";
    //char url[]="rtsp://admin:admin@192.168.43.1/stream/main";
    //char url[]="rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp";

    char url[]="udp://224.1.1.1:5001";

    //char url[]="rtmp://192.168.1.100:1935/live/desktop";

    if (avformat_open_input(&pFormatCtx, url, NULL, &avdic) != 0) {
        qDebug("can't open the file. \n");
        return;
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        qDebug("Could't find stream infomation.\n");
        return;
    }

    videoStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }

    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        qDebug("Didn't find a video stream.\n");
        return;
    }

    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    ///2017.8.9---lizhen
    //pCodecCtx->bit_rate = 0;   //初始化为0
    //pCodecCtx->time_base.num = 1;  //下面两行:一秒钟25帧
    //pCodecCtx->time_base.den = 10;
    //pCodecCtx->frame_number = 1;  //每包一个视频帧

    if (pCodec == NULL) {
        qDebug("Codec not found.\n");
        return;
    }

    ///打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        qDebug("Could not open codec.\n");
        return;
    }

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    ///这里我们改成了 将解码后的YUV数据转换成RGB32
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
            pCodecCtx->width, pCodecCtx->height);

    int y_size = pCodecCtx->width * pCodecCtx->height;

    packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
    av_new_packet(packet, y_size); //分配packet的数据
    //*/

    //bool CanRun = true;

    //ffmpeg 初始化
    // 初始化注册ffmpeg相关的编码器
    av_register_all();
    avcodec_register_all();
    avformat_network_init();

    //qDebug()<<"1 FFmpeg version info: {  av_version_info() }";
    qDebug()<<"2 FFmpeg version info: { " << av_version_info() << "}";
    qDebug()<<"3 FFmpeg version info:";
    qDebug()<< av_version_info() ;

    char url[]="udp://224.1.1.1:5001";

    // ffmpeg 转码
    // 分配音视频格式上下文
    AVFormatContext *pFormatCtx = avformat_alloc_context();

    AVDictionary* avdic = NULL;
    av_dict_set(&avdic, "buffer_size", "8192000", 0);
    av_dict_set(&avdic, "max_interleave_delta", "40000", 0);

    av_dict_set(&avdic, "analyzeduration", "100000000", 0);
    av_dict_set(&avdic, "probesize", "100000000", 0);

    int ret;
    //打开流
    ret = avformat_open_input(&pFormatCtx, url, NULL, &avdic);
    if (ret != 0){ qDebug("can't open the url. \n"); return; }

    // 读取媒体流信息
    ret = avformat_find_stream_info(pFormatCtx, NULL);
    if (ret != 0) { qDebug("Could't find stream infomation.\n"); return; }

    // 这里只是为了打印些视频参数
    AVDictionaryEntry* tag = NULL;
    while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) != NULL)
    {
        char * key = tag->key;
        char * value = tag->value;
        qDebug()<< *key  << *value << "\n";
    }

    // 从格式化上下文获取流索引
    AVStream* pStream = NULL;
    //AVStream* aStream = NULL;
    int videoStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            pStream = pFormatCtx->streams[i];
            videoStream = i;
        }
        //else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        //{
        //    aStream = pFormatCtx->streams[i];
        //}
    }
    if (pStream == NULL) { qDebug("Didn't find a video stream.\n"); return; }

    // 获取流的编码器上下文
    AVCodecContext codecContext = *pStream->codec;

    qDebug() << "codec name: {" << avcodec_get_name(codecContext.codec_id) << "}\n";
    // 获取图像的宽、高及像素格式
    int width = codecContext.width;
    int height = codecContext.height;
    AVPixelFormat sourcePixFmt = codecContext.pix_fmt;

    // 得到编码器ID
    AVCodecID codecId = codecContext.codec_id;
    // 目标像素格式
    AVPixelFormat destinationPixFmt = AV_PIX_FMT_RGB32;

    // 某些264格式codecContext.pix_fmt获取到的格式是AV_PIX_FMT_NONE 统一都认为是YUV420P
    if (sourcePixFmt == AV_PIX_FMT_NONE && codecId == AV_CODEC_ID_MPEG2TS)
    {
        sourcePixFmt = AV_PIX_FMT_YUV420P;
    }

    static struct SwsContext *img_convert_ctx;

    // 得到SwsContext对象:用于图像的缩放和转换操作
    img_convert_ctx = sws_getContext(width, height, sourcePixFmt,
        width, height, destinationPixFmt,
        SWS_FAST_BILINEAR, NULL, NULL, NULL);
    if (img_convert_ctx == NULL) { qDebug() << "Could not initialize the conversion context.\n" ; return; }

    //分配一个默认的帧对象:AVFrame
    //AVFrame* pConvertedFrame = av_frame_alloc();
    // 目标媒体格式需要的字节长度
    //numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
    int numBytes = avpicture_get_size(destinationPixFmt, width, height);
    // 分配目标媒体格式内存使用
    //out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    uint8_t * out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    //var dstData = new byte_ptrArray4();
    //var dstLinesize = new int_array4();
    AVFrame *pFrameRGB = av_frame_alloc();
    // 设置图像填充参数
    //av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, convertedFrameBufferPtr, destinationPixFmt, width, height, 1);
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, destinationPixFmt,width, height);

    // ffmpeg 解码
    // 根据编码器ID获取对应的解码器
    AVCodec* pCodec = avcodec_find_decoder(codecId);
    if (pCodec == NULL) { qDebug() << "Unsupported codec.\n"; }

    AVCodecContext* pCodecCtx = &codecContext;

    if ((pCodec->capabilities & AV_CODEC_CAP_TRUNCATED) == AV_CODEC_CAP_TRUNCATED)
        pCodecCtx->flags |= AV_CODEC_FLAG_TRUNCATED;

    // 通过解码器打开解码器上下文:AVCodecContext pCodecContext
    ret = avcodec_open2(pCodecCtx, pCodec, NULL);
    if (ret < 0) { qDebug() << ret << "\n"; return; }

    // 分配解码帧对象:AVFrame pDecodedFrame
    AVFrame* pFrame = av_frame_alloc();

    // 初始化媒体数据包
    AVPacket* packet = new AVPacket();
    AVPacket** pPacket = &packet;
    av_init_packet(packet);

    while (1)
    {
        /*
        if (av_read_frame(pFormatCtx, packet) < 0)
        {
            qDebug() << "av_read_frame < 0";
            break; //这里认为视频读取完了
        }

        if (packet->stream_index == videoStream) {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

            if (ret < 0) {
                qDebug("decode error.\n");
                return;
            }

            if (got_picture) {
                sws_scale(img_convert_ctx,
                        (uint8_t const * const *) pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                        pFrameRGB->linesize);

                //把这个RGB数据 用QImage加载
                QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
                emit sig_GetOneFrame(image);  //发送信号
                //emit sig_GetOneFrame(tmpImg);


                //提取出图像中的R数据
                //for(int i=0;i<pCodecCtx->width;i++)
                //{
                //    for(int j=0;j<pCodecCtx->height;j++)
                //    {
                //        QRgb rgb=image.pixel(i,j);
                //        int r=qRed(rgb);
                //        image.setPixel(i,j,qRgb(r,0,0));
                //    }
                //}
                //emit sig_GetRFrame(image);


            }else qDebug() << "got_picture < 0";
        }else qDebug() << "packet->stream_index not video stream";
        av_free_packet(packet); //释放资源,否则内存会一直上升
        //msleep(0.02); //停一停  不然放的太快了
        //*/

        //*
        try
        {
            do
            {
                // 读取一帧未解码数据
                ret = av_read_frame(pFormatCtx, packet);
               // Console.WriteLine(pPacket->dts);
                if (ret == AVERROR_EOF) break;
                if (ret < 0) { qDebug() << "got error "; return; }

                if (packet->stream_index != videoStream) continue;

                // 解码
                ret = avcodec_send_packet(pCodecCtx, packet);
                if (ret < 0) { qDebug() << "got error 2 "; return; }
                // 解码输出解码数据
                ret = avcodec_receive_frame(pCodecCtx, pFrame);
            } while (ret == AVERROR(EAGAIN) && 1);
            if (ret == AVERROR_EOF) break;
            if (ret < 0) { qDebug() << "got error 3 "; return; }

            if (packet->stream_index != videoStream) continue;

            //Console.WriteLine($@"frame: {frameNumber}");
            // YUV->RGB
            sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

            //把这个RGB数据 用QImage加载
            QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
            QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
            emit sig_GetOneFrame(image);  //发送信号
            //emit sig_GetOneFrame(tmpImg);

        }  catch (exception e) {

        }
        {
            av_packet_unref(packet);//释放数据包对象引用
            av_frame_unref(pFrame);//释放解码帧对象引用
        }
        //*/
    }
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

相关文章:

Qt调用FFmpeg库实时播放UDP组播视频流

基于以下参考链接&#xff0c;通过改进实现实时播放UDP组播视频流 https://blog.csdn.net/u012532263/article/details/102736700 源码在windows&#xff08;qt-opensource-windows-x86-5.12.9.exe&#xff09;、ubuntu20.04.6(x64)(qt-opensource-linux-x64-5.12.12.run)、以…...

Python学习之旅:进阶阶段(五)数据结构-双端队列(collections.deque)

在 Python 的进阶学习过程中,数据结构的掌握至关重要。今天要介绍的双端队列(deque,即 double-ended queue),是一种非常实用的数据结构,Python 的collections模块中的deque类为我们提供了强大的双端队列操作功能。接下来,就一起深入了解双端队列吧。 一、什么是双端队列…...

selenium自动化测试框架——面试题整理

目录 1. 什么是 Selenium&#xff1f;它的工作原理是什么&#xff1f; 2. Selenium 主要组件 3. 常见 WebDriver 驱动 4. Selenium 如何驱动浏览器&#xff1f; 5. WebDriver 协议是什么&#xff1f; 6. Page Object 模式与 Page Factory 7. 如何判断元素是否可见&#x…...

第19篇:python高级编程进阶:使用Flask进行Web开发

第19篇&#xff1a;python高级编程进阶&#xff1a;使用Flask进行Web开发 内容简介 在第18篇文章中&#xff0c;我们介绍了Web开发的基础知识&#xff0c;并使用Flask框架构建了一个简单的Web应用。本篇文章将深入探讨Flask的高级功能&#xff0c;涵盖模板引擎&#xff08;Ji…...

深度学习框架应用开发:基于 TensorFlow 的函数求导分析

深度学习框架应用开发&#xff1a;基于 TensorFlow 的函数求导分析 在深度学习的世界里&#xff0c;梯度计算是优化算法的核心。而 TensorFlow 作为一款强大的深度学习框架&#xff0c;为我们提供了简洁而强大的工具来进行自动求导操作&#xff0c;这极大地简化了深度学习模型的…...

【学术会议征稿-第二届生成式人工智能与信息安全学术会议(GAIIS 2025)】人工智能与信息安全的魅力

重要信息 时间&#xff1a;2025年2月21日-23日 地点&#xff1a;中国杭州 官网&#xff1a;http://www.ic-gaiis.org 简介 2025年第二届生成式人工智能与信息安全将于 2025年2月21日-23日在中国杭州举行。主要围绕“生成式人工智能与信息安全”的最新研究展开&#xff0c;…...

2025春晚刘谦魔术揭秘魔术过程

2025春晚刘谦魔术揭秘魔术过程 首先来看全过程 将杯子&#xff0c;筷子&#xff0c;勺子以任意顺序摆成一排 1.筷子和左边物体交换位置 2.杯子和右边物体交换位置 3.勺子和左边物体交换位置 最终魔术的结果是右手出现了杯子 这个就是一个简单的分类讨论的问题。 今年的魔术…...

postgresql的用户、数据库和表

在 PostgreSQL 中&#xff0c;用户、数据库和表是关系型数据库系统的基本组成部分。理解这些概念对数据库管理和操作至关重要。下面是对这些概念的详细解释&#xff1a; 1. 用户&#xff08;User&#xff09; 在 PostgreSQL 中&#xff0c;用户&#xff08;也称为 角色&#…...

上海亚商投顾:沪指冲高回落 大金融板块全天强势 上海亚商投

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一&#xff0e;市场情绪 市场全天冲高回落&#xff0c;深成指、创业板指午后翻绿。大金融板块全天强势&#xff0c;天茂集团…...

06-机器学习-数据预处理

数据清洗 数据清洗是数据预处理的核心步骤&#xff0c;旨在修正或移除数据集中的错误、不完整、重复或不一致的部分&#xff0c;为后续分析和建模提供可靠基础。以下是数据清洗的详细流程、方法和实战示例&#xff1a; 一、数据清洗的核心任务 问题类型表现示例影响缺失值数值…...

01学习预热篇(D6_正式踏入JVM深入学习前的铺垫)

目录 学习前言 一、虚拟机的结构 1. Java虚拟机参数设置 2. java 堆 3. 出入栈 4. 局部变量表 1> 局部变量的剖析 2> 局部变量的回收 5. 操作数栈 1> 常量入栈指令 2> 局部变量值转载到栈中指令 3> 将栈顶值保存到局部变量中指令 6. 帧数据区 7. 栈…...

【漫话机器学习系列】068.网格搜索(GridSearch)

网格搜索&#xff08;Grid Search&#xff09; 网格搜索&#xff08;Grid Search&#xff09;是一种用于优化机器学习模型超参数的技术。它通过系统地遍历给定的参数组合&#xff0c;找出使模型性能达到最优的参数配置。 网格搜索的核心思想 定义参数网格 创建一个包含超参数值…...

https数字签名手动验签

以bing.com 为例 1. CA 层级的基本概念 CA 层级是一种树状结构&#xff0c;由多个层级的 CA 组成。每个 CA 负责为其下一层级的实体&#xff08;如子 CA 或终端实体&#xff09;颁发证书。层级结构的顶端是 根 CA&#xff08;Root CA&#xff09;&#xff0c;它是整个 PKI 体…...

【股票数据API接口36】如何获取股票当天逐笔大单交易数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据

​ 如今&#xff0c;量化分析在股市领域风靡一时&#xff0c;其核心要素在于数据&#xff0c;获取股票数据&#xff0c;是踏上量化分析之路的第一步。你可以选择亲手编写爬虫来抓取&#xff0c;但更便捷的方式&#xff0c;莫过于利用专业的股票数据API接口。自编爬虫虽零成本&a…...

RocketMQ 中如何实现消息的可靠传递?

引言 作为头部消息队列开源中间件&#xff0c;学习其中的技术方案并且总结可靠性和健壮性&#xff0c;提升我们的架构思维和解决问题的能力 。 在 RocketMQ 中实现消息的可靠传递可以从多个方面入手&#xff0c;涵盖生产者、Broker 以及消费者等不同环节。 生产者端 1. 同步…...

Elasticsearch+kibana安装(简单易上手)

下载ES( Download Elasticsearch | Elastic ) 将ES安装包解压缩 解压后目录如下: 修改ES服务端口&#xff08;可以不修改&#xff09; 启动ES 记住这些内容 验证ES是否启动成功 下载kibana( Download Kibana Free | Get Started Now | Elastic ) 解压后的kibana目…...

视频多模态模型——视频版ViT

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细解读多模态论文《ViViT: A Video Vision Transformer》&#xff0c;2021由google 提出用于视频处理的视觉 Transformer 模型&#xff0c;在视频多模态领域有…...

单机伪分布Hadoop详细配置

目录 1. 引言2. 配置单机Hadoop2.1 下载并解压JDK1.8、Hadoop3.3.62.2 配置环境变量2.3 验证JDK、Hadoop配置 3. 伪分布Hadoop3.1 配置ssh免密码登录3.2 配置伪分布Hadoop3.2.1 修改hadoop-env.sh3.2.2 修改core-site.xml3.2.3 修改hdfs-site.xml3.2.4 修改yarn-site.xml3.2.5 …...

Ollama windows安装

Ollama 是一个开源项目&#xff0c;专注于帮助用户本地化运行大型语言模型&#xff08;LLMs&#xff09;。它提供了一个简单易用的框架&#xff0c;让开发者和个人用户能够在自己的设备上部署和运行 LLMs&#xff0c;而无需依赖云服务或外部 API。这对于需要数据隐私、离线使用…...

鸿蒙next 自定义日历组件

效果图预览 20250124-113957 使用说明 1.选择日期左右箭头&#xff0c;实现每月日历切换&#xff0c;示例中超出当前月份&#xff0c;禁止进入下一月&#xff0c;可在代码更改 2.日历中显示当前选择的日期&#xff0c;选中的日期颜色可自定义 3.日历中可展示历史记录作为数据…...

Nginx 开发总结

文章目录 1. Nginx 基础概念1-1、什么是 Nginx1-2、Nginx 的工作原理1-3、Nginx 的核心特点1-4、Nginx 的常见应用场景1-5、Nginx 与 Apache 的区别1-6、 Nginx 配置的基本结构1-7、Nginx 常见指令 2. Nginx 配置基础2-1、Nginx 配置文件结构2-2、全局配置 (Global Block)2-3、…...

Van-Nav:新年,将自己学习的项目地址统一整理搭建自己的私人导航站,供自己后续查阅使用,做技术的同学应该都有一个自己网站的梦想

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 Van-Nav是一个基于Vue.js开发的导航组件库&#xff0c;它提供了多种预设的样式和灵活的配置选项&#xff0c;使得开发者可以轻松地定制出符合项目需求…...

层次聚类构建层次结构的簇

层次聚类&#xff08;Hierarchical Clustering&#xff09;可以通过自定义函数来完成。层次聚类可以分为两种方法&#xff1a;凝聚型&#xff08;Agglomerative&#xff09;和分裂型&#xff08;Divisive&#xff09;。这里主要介绍一种常用的凝聚型方法&#xff0c;它是自底向…...

计算机网络__基础知识问答

Question: 1&#xff09;在计算机网络的5层结构中&#xff0c;每一层的功能大概是什么&#xff1f; 2&#xff09;交换机的功能&#xff1f;https://www.bilibili.com/video/BV1na4y1L7Ev 3&#xff09;路由器的功能&#xff1f;https://www.bilibili.com/video/BV1hv411k7n…...

网易云音乐歌名可视化:词云生成与GitHub-Pages部署实践

引言 本文将基于前一篇爬取的网易云音乐数据, 利用Python的wordcloud、matplotlib等库, 对歌名数据进行深入的词云可视化分析. 我们将探索不同random_state对词云布局的影响, 并详细介绍如何将生成的词云图部署到GitHub Pages, 实现数据可视化的在线展示. 介绍了如何从原始数据…...

PHP根据IP地址获取地理位置城市和经纬度信息

/** 根据IP地址 获取地理位置*/ function getLocationByIP($ip) {$url "http://ip-api.com/json/{$ip}?langzh-CN&fieldsstatus,message,country,countryCode,region,regionName,city,lat,lon,timezone,isp,org,as";$response file_get_contents($url);$data …...

渲染流程概述

渲染流程包括 CPU应用程序端渲染逻辑 和 GPU渲染管线 一、CPU应用程序端渲染逻辑 剔除操作对物体进行渲染排序打包数据调用Shader SetPassCall 和 Drawcall 1.剔除操作 视椎体剔除 &#xff08;给物体一个包围盒&#xff0c;利用包围盒和摄像机的视椎体进行碰撞检测&#xf…...

【单细胞-第三节 多样本数据分析】

文件在单细胞\5_GC_py\1_single_cell\1.GSE183904.Rmd GSE183904 数据原文 1.获取临床信息 筛选样本可以参考临床信息 rm(list ls()) library(tinyarray) a geo_download("GSE183904")$pd head(a) table(a$Characteristics_ch1) #统计各样本有多少2.批量读取 学…...

libOnvif通过组播不能发现相机

使用libOnvif库OnvifDiscoveryClient类&#xff0c; auto discovery new OnvifDiscoveryClient(QUrl(“soap.udp://239.255.255.250:3702”), cb.Build()); 会有错误&#xff1a; end of file or no input: message transfer interrupted or timed out(30 sec max recv delay)…...

项目集成GateWay

文章目录 1.环境搭建1.创建sunrays-common-cloud-gateway-starter模块2.目录结构3.自动配置1.GateWayAutoConfiguration.java2.spring.factories 3.pom.xml4.注意&#xff1a;GateWay不能跟Web一起引入&#xff01; 1.环境搭建 1.创建sunrays-common-cloud-gateway-starter模块…...