【音视频】FFmpeg解封装

解封装

复用器,比如MP4/FLV

在这里插入图片描述

解复用器,MP4/FLV

在这里插入图片描述

封装格式相关函数

  • avformat_alloc_context(); 负责申请一个AVFormatContext结构的内存,并进行简单初始化
  • avformat_free_context(); 释放该结构里的所有东西以及该结构本身
  • avformat_close_input();关闭解复用器。关闭后就不再需要使用avformat_free_context 进行释放。
  • avformat_open_input(); 打开输入视频文件
  • avformat_find_stream_info(): 获取视频文件信息
  • av_read_frame(); 读取音视频包
  • avformat_seek_file(); 定位文件
  • av_seek_frame(): 定位文件

解封装流程

在这里插入图片描述

FFmpeg数据结构之间的关系

区分不同的码流
  • AVMEDIA_TYPE_VIDEO视频流
video_index = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,-1,-1, NULL, 0)
  • AVMEDIA_TYPE_AUDIO 音频流
video_index = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,-1,-1, NULL, 0)

AVPacket 里面也有一个index的字段,这个字段存储对应的一个流

重点

  • avformat_open_inputavformat_find_stream_info分别用于打开一个流和分析流信息。
  • 在初始信息不足的情况下(比如FLVH264文件),avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据(音视频帧),然后再分析后,设置核心数据结构AVFormatContext
  • 由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟

实现流程

添加视频文件
  • build路径下添加相关mp4flvts文件
    在这里插入图片描述

  • 设置参数输入

![[Pasted image 20250330141825.png|600]]、

avformat_open_input
int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);

这个函数主要是用于打开输入的媒体文件或流,并对相关上下文进行初始化:

  • AVFormatContext结构体分配内存,然后将其地址存储在 *ps 中,后续对媒体文件的操作,如读取数据包、查找流信息等,都要基于这个上下文。

    • 输入媒体文件的文件名或 URL。可以是本地文件的路径,例如 "/path/to/your/file.mp4";也可以是网络流的 URL,比如 "http://example.com/stream.m3u8"
  • 第三个参数用于指定输入文件的格式。通常情况下设置为 NULL,让 FFmpeg 自动探测文件格式。不过,在某些特殊情况下,如果你明确知道文件的格式,也可以指定具体的 AVInputFormat 类型。

  • 最后一个参数是指向 AVDictionary 指针的指针,用于传递额外的选项,例如编解码器选项、协议选项等。如果不需要传递额外选项,可以将其设置为 NULL

  • 输入为mp4文件的时候,这一步可以获取很多信息,如编解码ID、媒体持续时间等

![[Pasted image 20250330142428.png|200]]

  • 输入文件为flv,则无法获取较多信息,因为flv头部信息不足

比如这里的duration没有被设置,是随机值

在这里插入图片描述

  • 输入为ts,效果和flv类似,但信息比flv多一点

在这里插入图片描述

avformat_find_stream_info

这个函数主要是用于分析输入媒体的流信息,为上下文结构体补充信息,比如比特率等

  • 在调用 avformat_open_input 打开媒体文件后,AVFormatContext 中仅包含了一些基本的文件信息,而各个流(如音频流、视频流、字幕流等)的详细信息,像编码格式、帧率、采样率、分辨率等,往往还未被解析出来。avformat_find_stream_info 函数的主要功能就是读取媒体文件的一定数量的数据包,对这些数据包进行分析,从而填充 AVFormatContext 中各流的详细信息。
ret = avformat_find_stream_info(ifmt_ctx, NULL);

参考下图,执行后将bite_rateduration等信息补充到了上下文中

在这里插入图片描述

如果一开始头部信息不足,调用这个函数比较耗费时间,因为需要在内部读入视频帧进行分析

  • ts文件
    在这里插入图片描述

  • flv文件

请添加图片描述

mp4文件

在这里插入图片描述

可以发现,是存在延迟的,如果文件容量更大的话,延迟可能会更大

av_dump_format

这个函数将上下文信息打印出来

av_dump_format(ifmt_ctx, 0, in_filename, 0);
  • 使用avformat_open_input后打印上下文信息

请添加图片描述

  • 使用avformat_find_stream_info后打印信息

请添加图片描述

这里还可以将上下文结构体的内容打印出来:
请添加图片描述

 // url: 调用avformat_open_input读取到的媒体文件的路径/名字printf("media name:%s\n", ifmt_ctx->url);// nb_streams: nb_streams媒体流数量printf("stream number:%d\n", ifmt_ctx->nb_streams);// bit_rate: 媒体文件的码率,单位为bpsprintf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024));// 时间int total_seconds, hour, minute, second;// duration: 媒体文件时长,单位微妙total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒hour = total_seconds / 3600;minute = (total_seconds % 3600) / 60;second = (total_seconds % 60);//通过上述运算,可以得到媒体文件的总时长printf("total duration: %02d:%02d:%02d\n", hour, minute, second);printf("\n");

注意这里的duration为媒体总时长,单位为微秒,因此转换为秒需要除以1e6AV_TIME_BASE宏对应就是1e6

![[Pasted image 20250330145622.png|400]]

获取相应流

可以通过遍历上下文中对应的二维流数组来找到自己想要的流,比如音频流和视频流

AVStream 是 FFmpeg 库中一个关键的结构体,主要用于描述媒体文件中的一个流(例如视频流、音频流、字幕流等),结构体内存储了很多关于流的信息:

AVRational time_baseAVRational 是一个表示分数的结构体,time_base 定义了流中时间戳的基本单位。时间戳(如 PTSDTS)是以 time_base 为单位的。例如,若 time_base{1, 1000},则表示时间戳的单位是 1/1000 秒。在进行时间戳的转换和计算时,需要使用这个 time_baseAVCodecParameters 结构体包含了流的编解码器相关的参数信息,如视频流的分辨率、帧率、像素格式,音频流的采样率、声道数、采样格式等。这些信息对于选择合适的解码器以及进行解码操作非常重要。

1. int index

此成员表示该流在 AVFormatContextstreams 数组中的索引。在处理多个流的媒体文件时,可以通过这个索引来区分不同的流。例如,在读取数据包时,AVPacket 结构体中的 stream_index 就会指向这个索引,以此确定该数据包属于哪个流。

2. AVRational time_base

AVRational 是一个表示分数的结构体,time_base 定义了流中时间戳的基本单位。时间戳(如 PTSDTS)是以 time_base 为单位的。例如,若 time_base{1, 1000},则表示时间戳的单位是 1/1000 秒。在进行时间戳的转换和计算时,需要使用这个 time_base

3. AVCodecParameters *codecpar

AVCodecParameters 结构体包含了流的编解码器相关的参数信息,如视频流的分辨率、帧率、像素格式,音频流的采样率、声道数、采样格式等。这些信息对于选择合适的解码器以及进行解码操作非常重要。

4. int64_t duration

该成员表示流的总时长,单位是 time_base。要将其转换为秒,可以使用以下公式:duration_in_seconds = (double)duration * av_q2d(time_base)

5. int64_t start_time

表示流的起始时间,单位同样是 time_base。在某些情况下,流的起始时间可能不是从 0 开始的,通过这个成员可以获取到流实际的起始时间。

6. AVRational avg_frame_rateAVRational r_frame_rate
  • avg_frame_rate 表示流的平均帧率,是根据流中所有帧的时间间隔计算得出的平均帧率。
  • r_frame_rate 表示流的实际帧率,通常是固定的帧率,对于一些固定帧率的视频流,这个值会比较准确。
7. void *priv_data

这是一个指向私有数据的指针,用于存储特定格式的额外信息。不同的格式可能会使用这个指针来存储一些自定义的数据,一般情况下不需要直接操作这个指针。

在我们的示例中,通过遍历上下文所有的流,每个流都有唯一对应的流索引,因此可以通过流中的编解码参数信息,打印出相应的音视频格式:

  • 获取当前索引的流结构体
AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
  • 通过编解码器的参数获取编解码类型,返回相应的类型宏定义
  1. 音频MEDIA_TYPE_AUDIO
if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
  1. 视频 MEDIA_TYPE_VIDEO
else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  
  • 如果是音频,可以打印出相关的音频信息,如采样率、采样格式(如FLTPS16P)、通道数、压缩格式(如AACMP3)、音频总时长等
 printf("----- Audio info:\n");// index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识printf("index:%d\n", in_stream->index);// sample_rate: 音频编解码器的采样率,单位为Hzprintf("samplerate:%dHz\n", in_stream->codecpar->sample_rate);// codecpar->format: 音频采样格式if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");}else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_S16P\n");}// channels: 音频信道数目printf("channel number:%d\n", in_stream->codecpar->channels);// codec_id: 音频压缩编码格式if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id){printf("audio codec:AAC\n");}else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id){printf("audio codec:MP3\n");}else{printf("audio codec_id:%d\n", in_stream->codecpar->codec_id);}// 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微秒,音频总时长跟视频总时长不一定相等的if(in_stream->duration != AV_NOPTS_VALUE){int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);//将音频总时长转换为时分秒的格式打印到控制台上printf("audio duration: %02d:%02d:%02d\n",duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));}else{printf("audio duration unknown");}printf("\n");
  • 注意,在计算音频时长的时候,AVStream中的duration和上下文AVFormatContext中的单位不一样,这里的单位是时间基time_base,不同的媒体文件可能时间基不同,比如可能是1/1000 s作为一个时间基,那么我们转换为妙就需要如下操作
    s = A V S t r e a m − > d u r a t i o n ∗ a v _ q 2 d ( A V S t r e a m − > t i m e _ b a s e ) s = AVStream->duration*av\_q2d(AVStream->time\_base) s=AVStream>durationav_q2d(AVStream>time_base)

这里的av_q2d实际上就是将分数形式转换为double类型的小数形式,因此转换实质上上就是:duration* time_base

  • 如果是视频,同样可以提取出视频编解码器的信息,比如视频帧率(FPS)、视频压缩编码格式(H264MPEG4)、视频帧的宽高(1080x720),转换视频的持续时间的方式与音频一样,注意,time_base的值通常不同:

视频

  • 典型值{1, 25}25 FPS)、{1, 30}30 FPS)、{1, 90000}(精确时间基)
  • 含义:视频帧的时间间隔以帧率倒数为单位。

音频

  • 典型值{1, 44100}44.1kHz 采样率)、{1, 48000}48kHz 采样率)
  • 含义:音频帧的时间间隔以采样周期为单位
printf("----- Video info:\n");printf("index:%d\n", in_stream->index);// avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧printf("fps:%lffps\n", av_q2d(in_stream->avg_frame_rate));if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:MPEG4\n");}else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:H264\n");}else{printf("video codec_id:%d\n", in_stream->codecpar->codec_id);}// 视频帧宽度和帧高度printf("width:%d height:%d\n", in_stream->codecpar->width,in_stream->codecpar->height);//视频总时长,单位为秒。注意如果把单位放大为毫秒或者微秒,音频总时长跟视频总时长不一定相等的if(in_stream->duration != AV_NOPTS_VALUE){int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);printf("video duration: %02d:%02d:%02d\n",duration_video / 3600,(duration_video % 3600) / 60,(duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上}else{printf("video duration unknown");}printf("\n");
获取相应包(Packet

上下文中还存储了压缩的数据包,比如对应的H264AAC压缩包,我们可以读取这些压缩包

  • 首先我们需要为AVPacket结构体分配内存
 AVPacket *pkt = av_packet_alloc();
  • 通过一个循环来依次读取每一帧的数据包到AVPacket中,每次读取一帧后,内部的指针都会向后移动
while (1){ret = av_read_frame(ifmt_ctx, pkt);}
  • 判断数据包内的流索引(视频流、音频流),进行相应操作,如打印ptsdts、包的大小size、包对应文件的偏移量pos,以及根据不同的索引在不同AVStream中找到对应的当前帧的持续时间,如下
  1. 音频帧数据包持续时间
pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base)
  1. 视频帧数据包持续时间
pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base)
  • 解码完当前帧数据包后,需要将这一帧数据包释放,否则会导致内存泄漏,直接调用av_packet_unref减少引用计数即可,引用计数为0会自动释放帧数据包的buf内存
av_packet_unref(pkt);
  • 读取所有帧数据包之后,需要释放AVPacket结构体的内存
if(pkt)av_packet_free(&pkt);
释放内存

所有操作之后,需要释放上下文内存,并且关闭打开的文件或关闭对应网络流的连接

调用 avformat_close_input函数即可实现上述功能

if(ifmt_ctx)avformat_close_input(&ifmt_ctx);

整体代码

main.c

#include <stdio.h>
#include <libavformat/avformat.h>
#include<time.h>int main(int argc, char **argv)
{//打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
//    avformat_network_init();const char *default_filename = "believe.mp4";char *in_filename = NULL;if(argv[1] == NULL){in_filename = default_filename;}else{in_filename = argv[1];}printf("in_filename = %s\n", in_filename);//AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体AVFormatContext *ifmt_ctx = NULL;           // 输入文件的demuxint videoindex = -1;        // 视频索引int audioindex = -1;        // 音频索引// 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);if (ret < 0)  //如果打开媒体文件失败,打印失败原因{char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);printf("open %s failed:%s\n", in_filename, buf);goto failed;}printf_s("\n==== av_dump_format in_filename:%s ===\n", in_filename);av_dump_format(ifmt_ctx, 0, in_filename, 0);printf_s("\n==== av_dump_format finish =======\n\n");clock_t started = clock();ret = avformat_find_stream_info(ifmt_ctx, NULL);clock_t ended = clock();double elapsed_time = (double)(ended - started) / CLOCKS_PER_SEC;printf("avformat_find_stream_info took %f seconds to execute.\n", elapsed_time);if (ret < 0)  //如果打开媒体文件失败,打印失败原因{char buf[1024] = { 0 };av_strerror(ret, buf, sizeof(buf) - 1);printf("avformat_find_stream_info %s failed:%s\n", in_filename, buf);goto failed;}//打开媒体文件成功printf_s("\n==== av_dump_format in_filename:%s ===\n", in_filename);av_dump_format(ifmt_ctx, 0, in_filename, 0);printf_s("\n==== av_dump_format finish =======\n\n");// url: 调用avformat_open_input读取到的媒体文件的路径/名字printf("media name:%s\n", ifmt_ctx->url);// nb_streams: nb_streams媒体流数量printf("stream number:%d\n", ifmt_ctx->nb_streams);// bit_rate: 媒体文件的码率,单位为bpsprintf("media average ratio:%lldkbps\n",(int64_t)(ifmt_ctx->bit_rate/1024));// 时间int total_seconds, hour, minute, second;// duration: 媒体文件时长,单位微妙total_seconds = (ifmt_ctx->duration) / AV_TIME_BASE;  // 1000us = 1ms, 1000ms = 1秒hour = total_seconds / 3600;minute = (total_seconds % 3600) / 60;second = (total_seconds % 60);//通过上述运算,可以得到媒体文件的总时长printf("total duration: %02d:%02d:%02d\n", hour, minute, second);printf("\n");/** 老版本通过遍历的方式读取媒体文件视频和音频的信息* 新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果*/for (uint32_t i = 0; i < ifmt_ctx->nb_streams; i++){AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流//如果是音频流,则打印音频的信息if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type){printf("----- Audio info:\n");// index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识printf("index:%d\n", in_stream->index);// sample_rate: 音频编解码器的采样率,单位为Hzprintf("samplerate:%dHz\n", in_stream->codecpar->sample_rate);// codecpar->format: 音频采样格式if (AV_SAMPLE_FMT_FLTP == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_FLTP\n");}else if (AV_SAMPLE_FMT_S16P == in_stream->codecpar->format){printf("sampleformat:AV_SAMPLE_FMT_S16P\n");}// channels: 音频信道数目printf("channel number:%d\n", in_stream->codecpar->channels);// codec_id: 音频压缩编码格式if (AV_CODEC_ID_AAC == in_stream->codecpar->codec_id){printf("audio codec:AAC\n");}else if (AV_CODEC_ID_MP3 == in_stream->codecpar->codec_id){printf("audio codec:MP3\n");}else{printf("audio codec_id:%d\n", in_stream->codecpar->codec_id);}// 音频总时长,单位为秒。注意如果把单位放大为毫秒或者微秒,音频总时长跟视频总时长不一定相等的if(in_stream->duration != AV_NOPTS_VALUE){int duration_audio = (in_stream->duration) * av_q2d(in_stream->time_base);//将音频总时长转换为时分秒的格式打印到控制台上printf("audio duration: %02d:%02d:%02d\n",duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));}else{printf("audio duration unknown");}printf("\n");audioindex = i; // 获取音频的索引}else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)  //如果是视频流,则打印视频的信息{printf("----- Video info:\n");printf("index:%d\n", in_stream->index);// avg_frame_rate: 视频帧率,单位为fps,表示每秒出现多少帧printf("fps:%lffps\n", av_q2d(in_stream->avg_frame_rate));if (AV_CODEC_ID_MPEG4 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:MPEG4\n");}else if (AV_CODEC_ID_H264 == in_stream->codecpar->codec_id) //视频压缩编码格式{printf("video codec:H264\n");}else{printf("video codec_id:%d\n", in_stream->codecpar->codec_id);}// 视频帧宽度和帧高度printf("width:%d height:%d\n", in_stream->codecpar->width,in_stream->codecpar->height);//视频总时长,单位为秒。注意如果把单位放大为毫秒或者微秒,音频总时长跟视频总时长不一定相等的if(in_stream->duration != AV_NOPTS_VALUE){int duration_video = (in_stream->duration) * av_q2d(in_stream->time_base);printf("video duration: %02d:%02d:%02d\n",duration_video / 3600,(duration_video % 3600) / 60,(duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上}else{printf("video duration unknown");}printf("\n");videoindex = i;}}AVPacket *pkt = av_packet_alloc();int pkt_count = 0;int print_max_count = 10;printf("\n-----av_read_frame start\n");while (1){ret = av_read_frame(ifmt_ctx, pkt);if (ret < 0){printf("av_read_frame end\n");break;}if(pkt_count++ < print_max_count){if (pkt->stream_index == audioindex){printf("audio pts: %lld\n", pkt->pts);printf("audio dts: %lld\n", pkt->dts);printf("audio size: %d\n", pkt->size);printf("audio pos: %lld\n", pkt->pos);printf("audio duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base));}else if (pkt->stream_index == videoindex){printf("video pts: %lld\n", pkt->pts);printf("video dts: %lld\n", pkt->dts);printf("video size: %d\n", pkt->size);printf("video pos: %lld\n", pkt->pos);printf("video duration: %lf\n\n",pkt->duration * av_q2d(ifmt_ctx->streams[videoindex]->time_base));}else{printf("unknown stream_index:\n", pkt->stream_index);}}av_packet_unref(pkt);}if(pkt)av_packet_free(&pkt);
failed:if(ifmt_ctx)avformat_close_input(&ifmt_ctx);getchar(); //加上这一句,防止程序打印完信息马上退出return 0;
}

更多资料:https://github.com/0voice

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

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

相关文章

1️⃣5️⃣three.js_GUI辅助调试器

15、GUI辅助调试器 3D虚拟工厂在线体验 GUI辅助调试器将原本需要修改代码调整参数并刷新页面的操作&#xff0c;简化为直接在GUI中实时调整&#xff0c;实现所见即所得的效果。 导入GUI 库 //引入GUI辅助调试器 import { GUI } from three/addons/libs/lil-gui.module.min.js…

Redis 的指令执行方式:Pipeline、事务与 Lua 脚本的对比

Pipeline 客户端将多条命令打包发送&#xff0c;服务器顺序执行并一次性返回所有结果。可以减少网络往返延迟&#xff08;RTT&#xff09;以提升吞吐量。 需要注意的是&#xff0c;Pipeline 中的命令按顺序执行&#xff0c;但中间可能被其他客户端的命令打断。 典型场景&…

Linux下的网络管理配置

一、 IPv4原理 IPv4&#xff08;Internet Protocol version 4&#xff09;&#xff0c;采用32位地址。IPv4地址通常用点分十进制表示&#xff0c;如 192.168.1.10。 IPv4网络通信基于数据包交换原理&#xff0c;当一台主机要向另一台主机发送数据时&#xff0c;会将数据分割成…

基于Python(Django)+SQLite实现(Web)校园助手

校园助手 本校园助手采用 B/S 架构。并已将其部署到服务器上。在网址上输入 db.uplei.com 即可访问。 使用说明 可使用如下账号体验&#xff1a; 学生界面: 账号1&#xff1a;123 密码1&#xff1a;123 账户2&#xff1a;201805301348 密码2&#xff1a;1 # --------------…

unity动态骨骼架设+常用参数分享(包含部分穿模解决方案)

Unity骨骼物理模拟插件Dynamic Bone Dynamic Bone 可用于对角色的骨骼&#xff08;bones&#xff09;或者铰链系统&#xff08;joints&#xff09;施加物理效果。 物理效果可以使得游戏角色的头发、衣服、胸部或者是其他的任何部位&#xff0c;都可以以近似真实的状态运动。 …

科技天眼守望农田:珈和卫星遥感监测赋能智慧农业,护航粮食安全新未来

农情监测与粮食安全密切相关&#xff0c;以往农作物的长势、环境、病虫害、灾情等相关数据和图像信息都是靠物联网硬件及县、镇、村等人力来完成&#xff0c;不仅要耗费大量人力、物力&#xff0c;而且数据时效性、准确性较差。珈和科技开发建设农情遥感监测系统&#xff0c;运…

【TeamFlow】4.2 Yew库详细介绍

Yew 是一个用于构建高效、交互式前端 Web 应用程序的现代 Rust 框架&#xff0c;它借鉴了 React 和 Elm 等框架的设计理念&#xff0c;同时充分利用 Rust 的语言特性。 核心特性 基于组件的架构 Yew 采用组件化开发模式&#xff0c;类似于 React: 组件是可重用的 UI 构建块 …

毕设 - 数字孪生智慧农场(vue+高德地图)项目分享

感兴趣的同学可以私信我或者在下方添加我的qq 在线地址: 数字孪生智慧农场

深入理解 VMware 虚拟机网络模式:为虚拟化管理铺平道路

随着云计算和虚拟化技术的快速发展&#xff0c;VMware作为行业领军者&#xff0c;在企业的IT基础设施中扮演着越来越重要的角色。无论是开发、测试还是生产环境&#xff0c;虚拟机&#xff08;VM&#xff09;都成为了我们不可或缺的工具。在VMware中&#xff0c;网络是虚拟机能…

安恒安全渗透面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…

代码随想录算法训练营第五十三天 | 105.有向图的完全可达性 106.岛屿的周长

105.有向图的完全可达性 题目链接&#xff1a;101. 孤岛的总面积 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;图论&#xff1a;岛屿问题再出新花样 | 深搜优先搜索 | 卡码网&#xff1a;101.孤岛总面积_哔哩哔哩_bilibili 思路&#xff1a; 1.确认递归函数&…

蓝桥杯 18.分考场

分考场 原题目链接 题目描述 有 n 个人参加某项特殊考试。 为了公平&#xff0c;要求任何两个认识的人不能分在同一个考场。 你的任务是求出最少需要分几个考场才能满足这个条件。 输入描述 第一行&#xff1a;一个整数 n&#xff0c;表示参加考试的人数&#xff08;1 ≤…

分布式光纤测温技术让森林火灾预警快人一步

2025年春季&#xff0c;多地接连发生森林火灾&#xff0c;累计过火面积超 3万公顷。春季历来是森林草原火灾易发、多发期&#xff0c;加之清明节已到来&#xff0c;生产生活用火活跃&#xff0c;民俗祭祀用火集中&#xff0c;森林火灾风险进一步加大。森林防火&#xff0c;人人…

前端笔记-Vue3(上)

学习参考视频&#xff1a;尚硅谷Vue3入门到实战&#xff0c;最新版vue3TypeScript前端开发教程_哔哩哔哩_bilibili vue3学习目标&#xff1a; VUE 31、Vue3架构与设计理念2、组合式API&#xff08;Composition API&#xff09;3、常用API&#xff1a;ref、reactive、watch、c…

如何增加 Elasticsearch 中的 primary shard 数量

作者&#xff1a;来自 Elastic Kofi Bartlett 探索增加 Elasticsearch 中 primary shard 数量的方法。 更多阅读&#xff1a; Elasticsearch&#xff1a;Split index API - 把一个大的索引分拆成更多分片 Elasticsearch&#xff1a;通过 shrink API 减少 shard 数量来缩小 El…

基于SA模拟退火算法的车间调度优化matlab仿真,输出甘特图和优化收敛曲线

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于SA模拟退火算法的车间调度优化matlab仿真,输出甘特图和优化收敛曲线。输出指标包括最小平均流动时间&#xff0c;最大完工时间&#xff0c;最小间隙时间。 2…

Spring_MVC 快速入门指南

Spring_MVC 快速入门指南 一、Spring_MVC 简介 1. 什么是 Spring_MVC&#xff1f; Spring_MVC 是 Spring 框架的一个模块&#xff0c;用于构建 Web 应用程序。它基于 MVC&#xff08;Model-View-Controller&#xff09;设计模式&#xff0c;将应用程序分为模型&#xff08;M…

爬虫获取sku信息需要哪些库

在使用 Python 爬虫获取淘宝商品的 SKU 详细信息时&#xff0c;通常需要以下几种库来完成任务。这些库各有其用途&#xff0c;可以帮助你更高效地实现爬虫功能。 1. requests 用途&#xff1a;用于发送 HTTP 请求&#xff0c;获取网页内容。 安装&#xff1a; bash pip insta…

赛灵思Xilinx FPGa XCKU15P‑2FFVA1156I AMD Kintex UltraScale+

XCKU15P‑2FFVA1156I 是 AMD Kintex UltraScale 系列中的高性能 FPGA&#xff0c;基于 16 nm FinFET UltraScale 架构 制造&#xff0c;兼顾卓越的性能与功耗比&#xff0c;该器件集成 1,143,450 个逻辑单元和 82,329,600 位片上 RAM&#xff0c;配备 1,968 个 DSP 切片&#…

从规则到大模型:知识图谱信息抽取实体NER与关系RE任务近10年演进发展详解

摘要: 本文回顾了关系抽取与实体抽取领域的经典与新兴模型,清晰地梳理了它们的出现时间与核心创新,并给出在 2025 年不同资源与场景下的最佳实践推荐。文章引用了 BiLSTM‑CRF、BiLSTM‑CNN‑CRF、SpanBERT、LUKE、KnowBERT、CasRel、REBEL、UIE,大模型抽取 等模型的原始论…