【音视频】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_input和avformat_find_stream_info分别用于打开一个流和分析流信息。- 在初始信息不足的情况下(比如
FLV和H264文件),avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据(音视频帧),然后再分析后,设置核心数据结构AVFormatContext - 由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟
实现流程
添加视频文件
-
在
build路径下添加相关mp4,flv,ts文件

-
设置参数输入
![![[Pasted image 20250330141825.png|600]]、](https://i-blog.csdnimg.cn/direct/93619990ba084d7384b60a0d2026ce41.png)
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_rate、duration等信息补充到了上下文中

如果一开始头部信息不足,调用这个函数比较耗费时间,因为需要在内部读入视频帧进行分析
-
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为媒体总时长,单位为微秒,因此转换为秒需要除以1e6,AV_TIME_BASE宏对应就是1e6
![[Pasted image 20250330145622.png|400]]
获取相应流
可以通过遍历上下文中对应的二维流数组来找到自己想要的流,比如音频流和视频流
AVStream 是 FFmpeg 库中一个关键的结构体,主要用于描述媒体文件中的一个流(例如视频流、音频流、字幕流等),结构体内存储了很多关于流的信息:
AVRational time_base :AVRational 是一个表示分数的结构体,time_base 定义了流中时间戳的基本单位。时间戳(如 PTS 和 DTS)是以 time_base 为单位的。例如,若 time_base 为 {1, 1000},则表示时间戳的单位是 1/1000 秒。在进行时间戳的转换和计算时,需要使用这个 time_base。AVCodecParameters 结构体包含了流的编解码器相关的参数信息,如视频流的分辨率、帧率、像素格式,音频流的采样率、声道数、采样格式等。这些信息对于选择合适的解码器以及进行解码操作非常重要。
1. int index
此成员表示该流在 AVFormatContext 的 streams 数组中的索引。在处理多个流的媒体文件时,可以通过这个索引来区分不同的流。例如,在读取数据包时,AVPacket 结构体中的 stream_index 就会指向这个索引,以此确定该数据包属于哪个流。
2. AVRational time_base
AVRational 是一个表示分数的结构体,time_base 定义了流中时间戳的基本单位。时间戳(如 PTS 和 DTS)是以 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_rate 和 AVRational r_frame_rate
avg_frame_rate表示流的平均帧率,是根据流中所有帧的时间间隔计算得出的平均帧率。r_frame_rate表示流的实际帧率,通常是固定的帧率,对于一些固定帧率的视频流,这个值会比较准确。
7. void *priv_data
这是一个指向私有数据的指针,用于存储特定格式的额外信息。不同的格式可能会使用这个指针来存储一些自定义的数据,一般情况下不需要直接操作这个指针。
在我们的示例中,通过遍历上下文所有的流,每个流都有唯一对应的流索引,因此可以通过流中的编解码参数信息,打印出相应的音视频格式:
- 获取当前索引的流结构体
AVStream *in_stream = ifmt_ctx->streams[i];// 音频流、视频流、字幕流
- 通过编解码器的参数获取编解码类型,返回相应的类型宏定义
- 音频
MEDIA_TYPE_AUDIO
if (AVMEDIA_TYPE_AUDIO == in_stream->codecpar->codec_type)
- 视频
MEDIA_TYPE_VIDEO
else if (AVMEDIA_TYPE_VIDEO == in_stream->codecpar->codec_type)
- 如果是音频,可以打印出相关的音频信息,如采样率、采样格式(如
FLTP、S16P)、通道数、压缩格式(如AAC、MP3)、音频总时长等
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−>duration∗av_q2d(AVStream−>time_base)
这里的av_q2d实际上就是将分数形式转换为double类型的小数形式,因此转换实质上上就是:duration* time_base
- 如果是视频,同样可以提取出视频编解码器的信息,比如视频帧率(
FPS)、视频压缩编码格式(H264、MPEG4)、视频帧的宽高(1080x720),转换视频的持续时间的方式与音频一样,注意,time_base的值通常不同:
视频
- 典型值:
{1, 25}(25FPS)、{1, 30}(30FPS)、{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)
上下文中还存储了压缩的数据包,比如对应的H264、AAC压缩包,我们可以读取这些压缩包
- 首先我们需要为
AVPacket结构体分配内存
AVPacket *pkt = av_packet_alloc();
- 通过一个循环来依次读取每一帧的数据包到
AVPacket中,每次读取一帧后,内部的指针都会向后移动
while (1){ret = av_read_frame(ifmt_ctx, pkt);}
- 判断数据包内的流索引(视频流、音频流),进行相应操作,如打印
pts、dts、包的大小size、包对应文件的偏移量pos,以及根据不同的索引在不同AVStream中找到对应的当前帧的持续时间,如下
- 音频帧数据包持续时间
pkt->duration * av_q2d(ifmt_ctx->streams[audioindex]->time_base)
- 视频帧数据包持续时间
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
相关文章:
【音视频】FFmpeg解封装
解封装 复用器,比如MP4/FLV 解复用器,MP4/FLV 封装格式相关函数 avformat_alloc_context(); 负责申请一个AVFormatContext结构的内存,并进行简单初始化avformat_free_context(); 释放该结构里的所有东西以及该结构本身avformat_close_input();关闭解复…...
使用VHDL语言实现TXT文件的读写操作
使用FPGA进行图像处理时,通常需要将TXT文件中的图像数据读出到TestBench中,并将仿真的结果写入到TXT文件中,用于确认图像处理的结果是否正确。 VHDL中TXT文件的读写操作如下所示, --------------------------------------------…...
OpenLDAP 管理 ELK 用户
文章目录 一、新建 ELK 相关用户组二、配置 Elasticsearch2.1 修改 elasticsearch.yml 配置2.2 使用 API 接口建立角色和用户映射 三、Kibana 验证用户登录 一、新建 ELK 相关用户组 由于后续要将 LDAP 的用户与 ELK 的角色进行映射,所以需先创建几个以 ELK 的角色…...
第十七届“华中杯”大学生数学建模挑战赛题目A题 晶硅片产销策略优化 完整成品 代码 模型 思路 分享
近年来,高纯度晶硅片需求的增长引发了更激烈的市场竞争。晶硅片企业需要在成本控制、利润优化和供需管理之间取得平衡,以提高经营效率和市场竞争力。晶硅片的生产是一个高能耗、高成本的过程,企业效益会受到原材料价格波动、市场需求变化以及…...
iOS 中的虚拟内存 (理解为什么需要虚拟内存)
什么叫“虚拟地址空间”? 一句话:它是 CPU 看得见、App 以为自己独享,但实际上会被内核和硬件(MMU)动态翻译到真实 物理内存 的一整块“虚拟地图”。 1. 背景:为什么要“虚拟”? 需求虚拟地址空…...
网络层理解
网络层理解 网络层是 OSI 模型的第三层,主要负责 跨网络的数据传输,核心任务是 路由选择 和 分组转发。 网络层核心功能 网络层关键协议 协议作用示例IP (IPv4/IPv6)数据包路由和寻址192.168.1.1ICMP网络状态检测和错误报告ping、tracerouteOSPF/BGP动…...
代码随想录训练营38天 || 322. 零钱兑换 279. 完全平方数 139. 单词拆分
322. 零钱兑换 思路: 动规5部曲: 1.确定dp数组以及下标的含义: dp数组表示能凑出零钱的最少硬币数,下标表示要兑换的零钱 2.确定递推公式: j为背包容量,i为物品的下标 dp[ j ] min(dp[ j -coins[ i…...
从Archery到NineData:积加科技驱动数据库研发效能与数据安全双升级
积加科技作为国内领先的企业级数字化解决方案服务商,依托自研的 A4X 数字化平台(https://a4x.io/),专注于为全球范围内的视觉物联网(IoT)设备提供 PaaS/SaaS 服务。致力于运用 AI 技术赋能物联网世界的各类…...
C#委托介绍
委托可以将方法作为参数传递,同时委托也可以自己作为参数传递 委托可分为自定义委托delegate 无返回值的Action 与有返回值的Func委托 也有匿名委托与Lamada 委托支持多播是事件的基础 用处如在分线程调用主线程的UI invoke public delegate string Say(stri…...
解决splice改变原数组的BUG(拷贝数据)
项目场景: 项目中难免遇到需要删除改变数组的方法,去重,删除不要的数据等 问题描述: 但是splice方法会删除掉数据改变原数组,返回的是改变之后的数组,即使你是赋值的还是会影响到原数组的数据 GoodsInfo…...
齐次坐标系下的变换矩阵
理解齐次坐标系下的变换矩阵 文章目录 理解齐次坐标系下的变换矩阵1 引言2 齐次坐标系的简要介绍2.1 齐次坐标系的定义2.2 为什么需要齐次坐标系?2.3 齐次坐标系的特殊性质2.3.1 点和向量的区分2.3.2 投影变换 3 齐次坐标系下的变换矩阵3.1 二维变换矩阵平移变换缩放…...
开源Midjourney替代方案:企业级AI绘画+PPT生成系统+AI源码
「AI取代设计师?」开源Midjourney替代方案:企业级AI绘画PPT生成系统 ——零代码私有化部署,5倍速出图100%版权合规 设计师行业的危机与机遇 1. 传统设计流程的致命短板 痛点 人工设计 AI系统 单张海报耗时 3小时(含反复修改…...
2025.04.20【Lollipop】| Lollipop图绘制命令简介
Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目录 Customize markersCustomize stems Lollipop图简介R语言中的Lollipop图使用ggp…...
【Harmony】常用工具类封装
文章目录 一,简介二,网络请求工具类2.1、鸿蒙原生http封装2.2、第三方axios封装(需提前下载依赖) 三、录音笔相关工具类3.1、录音封装(录入)3.2、录音封装(放音/渲染)3.3、文件写入封装(针对录音/放音功能) 四、RDB关系型数据库4.1、relationalStore简答…...
DCDC芯片,boost升压电路设计,MT3608 芯片深度解析:从架构到设计的全维度技术手册
一、硬件架构解析:电流模式升压 converter 的核心设计 (一)电路拓扑与核心组件 MT3608 采用恒定频率峰值电流模式升压(Boost)转换器架构,核心由以下模块构成: 集成功率 MOSFET 内置 80mΩ 导通电阻的 N 沟道 MOSFET,漏极(Drain)对应引脚 SW,源极(Source)内部接…...
Cline 之Plan和Act模式
Cline 提供了 "Plan & Act"双模式开发框架。适用在不同的场景。 一、核心模式理念 通过结构化开发流程提升AI编程效率,采用"先规划后执行"的核心理念。 该框架旨在帮助开发者构建更易维护、准确性更高的代码,同时显著缩短开发…...
深入了解 WebGIS 系统:构建地理空间信息的网络桥梁
一、引言 在当今数字化时代,地理空间信息的获取、分析和共享变得越来越重要。WebGIS(网络地理信息系统,Web - based Geographic Information System)应运而生,它将地理信息系统(GIS)的强大功能…...
【中级软件设计师】程序设计语言基础成分
【中级软件设计师】程序设计语言基础成分 目录 【中级软件设计师】程序设计语言基础成分一、历年真题二、考点:程序设计语言基础成分1、基本成分2、数据成分3、控制成分 三、真题的答案与解析答案解析 复习技巧: 若已掌握【程序设计语言基础成分】相关知…...
C++项目 —— 基于多设计模式下的同步异步日志系统(3)(日志器类)
C项目 —— 基于多设计模式下的同步&异步日志系统(3)(日志器类) 整体思想设计日志消息的构造C语言式的不定参函数的作用函数的具体实现逻辑1. 日志等级检查2. 初始化可变参数列表3. 格式化日志消息4. 释放参数列表5. 序列化和…...
【数学建模】随机森林算法详解:原理、优缺点及应用
随机森林算法详解:原理、优缺点及应用 文章目录 随机森林算法详解:原理、优缺点及应用引言随机森林的基本原理随机森林算法步骤随机森林的优点随机森林的缺点随机森林的应用场景Python实现示例超参数调优结论参考文献 引言 随机森林是机器学习领域中一种…...
蓝桥杯 19.合根植物
合根植物 原题目链接 题目描述 W 星球的一个种植园被分成 m n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。 这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的…...
Linux环境MySQL出现无法启动的问题解决 [InnoDB] InnoDB initialization has started.
目录 起因 强制启用恢复模式 备份数据 起因 服务器重启了,然后服务器启动完成之后我发现MySQL程序没有启动,错误信息如下: 2025-04-19T12:46:47.648559Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2025-04-1…...
高性能服务器配置经验指南1——刚配置好服务器应该做哪些事
文章目录 安装ubuntu安装必要软件设置用户远程连接安全问题ClamAV安装教程步骤 1:更新系统软件源步骤 2:升级系统(可选但推荐)步骤 3:安装 ClamAV步骤 4:更新病毒库步骤 5:验证安装ClamAV 常用命…...
DePIN驱动的分布式AI资源网络
GAEA通过通证经济模型激励全球用户共享闲置带宽、算力、存储资源,构建覆盖150多个国家/地区的分布式AI基础设施网络。相比传统云服务,GAEA具有显著优势: 成本降低70%:通过利用边缘设备资源,避免了集中式数据中心所需…...
徐州服务器租用:虚拟主机的应用场景
虚拟主机也可以被称为网站空间或者是共享主机,主要是通过软硬件技术将一台物理服务器分割成多个逻辑单元的技术,让每一个单元都拥有着独立的IP地址和完整的网络服务功能,那么虚拟主机的应用场景都有哪些呢? 许多中小型企业会选择租…...
Centos7安装Jenkins(图文教程)
本章教程,主要记录在centos7安装部署Jenkins 的详细过程。 [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 一、基础环境安装 内存大小要求:256 MB 内存以上 硬盘大小要求:10 GB 及以上 安装基础java环境:Java 17 ( JRE 或者 JDK 都可…...
【JAVA】十三、基础知识“接口”精细讲解!(二)(新手友好版~)
哈喽大家好呀qvq,这里是乎里陈,接口这一知识点博主分为三篇博客为大家进行讲解,今天为大家讲解第二篇java中实现多个接口,接口间的继承,抽象类和接口的区别知识点,更适合新手宝宝们阅读~更多内容持续更新中…...
边缘计算盒子是什么?
边缘计算盒子是一种小型的硬件设备,通常集成了处理器、存储器和网络接口等关键组件,具备一定的计算能力和存储资源,并能够连接到网络。它与传统的云计算不同,数据处理和分析直接在设备本地完成,而不是上传到云端&#…...
量子计算在密码学中的应用与挑战:重塑信息安全的未来
在当今数字化时代,信息安全已成为全球关注的焦点。随着量子计算技术的飞速发展,密码学领域正面临着前所未有的机遇与挑战。量子计算的强大计算能力为密码学带来了新的应用场景,同时也对传统密码体系构成了潜在威胁。本文将深入探讨量子计算在…...
大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署--完结
大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署 1. ClickHouse与MySQL的区别2. 在群集的所有机器上安装ClickHouse服务端2.1. 在线安装clickhouse2.2. 离线安装clickhouse 3. ClickHouse Keeper/Zookeeper集群安装4. 在配置文件中设置集群配置5. 在每…...
