从头用脚分析FFmpeg源码 - av_read_frame
av_read_frame作用
/*** Return the next frame of a stream.* This function returns what is stored in the file, and does not validate* that what is there are valid frames for the decoder. It will split what is* stored in the file into frames and return one for each call. It will not* omit invalid data between valid frames so as to give the decoder the maximum* information possible for decoding.** On success, the returned packet is reference-counted (pkt->buf is set) and* valid indefinitely. The packet must be freed with av_packet_unref() when* it is no longer needed. For video, the packet contains exactly one frame.* For audio, it contains an integer number of frames if each frame has* a known fixed size (e.g. PCM or ADPCM data). If the audio frames have* a variable size (e.g. MPEG audio), then it contains one frame.** pkt->pts, pkt->dts and pkt->duration are always set to correct* values in AVStream.time_base units (and guessed if the format cannot* provide them). pkt->pts can be AV_NOPTS_VALUE if the video format* has B-frames, so it is better to rely on pkt->dts if you do not* decompress the payload.** @return 0 if OK, < 0 on error or end of file. On error, pkt will be blank* (as if it came from av_packet_alloc()).** @note pkt will be initialized, so it may be uninitialized, but it must not* contain data that needs to be freed.*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
上面说的很复杂,简单的来讲,就是从AVFormatContext读取一个AVPacket。
再看看雷神的补充:
通过av_read_packet(),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2()分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2()流程,而不是下面的av_read_packet()流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(**)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
av_read_frame源码
读取一个AVPacket,如果buffer里面存在,就从buffer中读,没有就调用read_frame_internal函数,从流中读。
int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{// AVFMT_FLAG_GENPTS是指在解封装阶段就要对packet设置pts。FFmpeg默认是不会设置的,// FFplay在使用的时候,可以通过genpts参数设置这个flag。一般的容器都会在解封装阶段自动设置// pts,这个标识其实是针对在容器中没有时间戳概念的结构。(比如h264裸流?)const int genpts = s->flags & AVFMT_FLAG_GENPTS;int eof = 0;int ret;AVStream *st;// 一般来说,都会走这里,因为没设置AVFMT_FLAG_GENPTS标志if (!genpts) {// 有buffer读buffer,没buffer就重新读。// avformat_find_stream_info中读取的AVPacket就会存放在buffer中。// read_frame_internal 才是真正从流中读取AVPacket的方法。ret = s->internal->packet_buffer? avpriv_packet_list_get(&s->internal->packet_buffer,&s->internal->packet_buffer_end, pkt): read_frame_internal(s, pkt);if (ret < 0)return ret;goto return_packet;}// 这里重新设置ptsfor (;;) {PacketList *pktl = s->internal->packet_buffer;if (pktl) {AVPacket *next_pkt = &pktl->pkt;// dts不能为null,pts需要从dts中推算出来if (next_pkt->dts != AV_NOPTS_VALUE) {int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;// last dts seen for this stream. if any of packets following// current one had no dts, we will set this to AV_NOPTS_VALUE.int64_t last_dts = next_pkt->dts;av_assert2(wrap_bits <= 64);while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {// 根据连续两个AVPacket之间的dts,设置ptsif (pktl->pkt.stream_index == next_pkt->stream_index &&av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2ULL << (wrap_bits - 1)) < 0) {if (av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2ULL << (wrap_bits - 1))) {// not B-framenext_pkt->pts = pktl->pkt.dts;}if (last_dts != AV_NOPTS_VALUE) {// Once last dts was set to AV_NOPTS_VALUE, we don't change it.last_dts = pktl->pkt.dts;}}pktl = pktl->next;}// 尾部加上durationif (eof && next_pkt->pts == AV_NOPTS_VALUE && last_dts != AV_NOPTS_VALUE) {// Fixing the last reference frame had none pts issue (For MXF etc).// We only do this when// 1. eof.// 2. we are not able to resolve a pts value for current packet.// 3. the packets for this stream at the end of the files had valid dts.next_pkt->pts = last_dts + next_pkt->duration;}pktl = s->internal->packet_buffer;}/* read packet from packet buffer, if there is data */st = s->streams[next_pkt->stream_index];// pts设置成功,并非是AV_NOPTS_VALUE,这个时候就应用这个AVPacketif (!(next_pkt->pts == AV_NOPTS_VALUE && st->discard < AVDISCARD_ALL &&next_pkt->dts != AV_NOPTS_VALUE && !eof)) {ret = avpriv_packet_list_get(&s->internal->packet_buffer,&s->internal->packet_buffer_end, pkt);goto return_packet;}}// buffer中没有,就继续读ret = read_frame_internal(s, pkt);if (ret < 0) {if (pktl && ret != AVERROR(EAGAIN)) {eof = 1;continue;} elsereturn ret;}//放到buffer中,待循环的时候使用ret = avpriv_packet_list_put(&s->internal->packet_buffer,&s->internal->packet_buffer_end,pkt, NULL, 0);if (ret < 0) {av_packet_unref(pkt);return ret;}}return_packet:st = s->streams[pkt->stream_index];if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY) {ff_reduce_index(s, st->index);av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, AVINDEX_KEYFRAME);}if (is_relative(pkt->dts))pkt->dts -= RELATIVE_TS_BASE;if (is_relative(pkt->pts))pkt->pts -= RELATIVE_TS_BASE;return ret;
}
read_frame_internal源码
读取一个AVPacket,读取完成之后,看是否需要parse,如果需要就parse一下,然后把parse的结果重新写入AVPacket中。
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{int ret, i, got_packet = 0;AVDictionary *metadata = NULL;// packet也有可能会放到s->internal->parse_queue中while (!got_packet && !s->internal->parse_queue) {AVStream *st;/* read next packet */// 读取一个packet,这是最主要的函数ret = ff_read_packet(s, pkt);if (ret < 0) {if (ret == AVERROR(EAGAIN))return ret;/* flush the parsers */// 如果需要parser,就parser,parser后,AVPacket会放入s->internal->parse_queue中for (i = 0; i < s->nb_streams; i++) {st = s->streams[i];if (st->parser && st->need_parsing)parse_packet(s, pkt, st->index, 1);}/* all remaining packets are now in parse_queue =>* really terminate parsing */break;}ret = 0;st = s->streams[pkt->stream_index];st->event_flags |= AVSTREAM_EVENT_FLAG_NEW_PACKETS;/* update context if required *///更新stream中的AVCodecContext信息,在hls,webm等格式中可能会用到if (st->internal->need_context_update) {if (avcodec_is_open(st->internal->avctx)) {av_log(s, AV_LOG_DEBUG, "Demuxer context update while decoder is open, closing and trying to re-open\n");avcodec_close(st->internal->avctx);st->internal->info->found_decoder = 0;}/* close parser, because it depends on the codec */if (st->parser && st->internal->avctx->codec_id != st->codecpar->codec_id) {av_parser_close(st->parser);st->parser = NULL;}ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);if (ret < 0) {av_packet_unref(pkt);return ret;}#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS/* update deprecated public codec context */ret = avcodec_parameters_to_context(st->codec, st->codecpar);if (ret < 0) {av_packet_unref(pkt);return ret;}
FF_ENABLE_DEPRECATION_WARNINGS
#endifst->internal->need_context_update = 0;}// 判断pts与输出if (pkt->pts != AV_NOPTS_VALUE &&pkt->dts != AV_NOPTS_VALUE &&pkt->pts < pkt->dts) {av_log(s, AV_LOG_WARNING,"Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n",pkt->stream_index,av_ts2str(pkt->pts),av_ts2str(pkt->dts),pkt->size);}if (s->debug & FF_FDEBUG_TS)av_log(s, AV_LOG_DEBUG,"ff_read_packet stream=%d, pts=%s, dts=%s, size=%d, duration=%"PRId64", flags=%d\n",pkt->stream_index,av_ts2str(pkt->pts),av_ts2str(pkt->dts),pkt->size, pkt->duration, pkt->flags);if (st->need_parsing && !st->parser && !(s->flags & AVFMT_FLAG_NOPARSE)) {st->parser = av_parser_init(st->codecpar->codec_id);if (!st->parser) {av_log(s, AV_LOG_VERBOSE, "parser not found for codec ""%s, packets or times may be invalid.\n",avcodec_get_name(st->codecpar->codec_id));/* no parser available: just output the raw packets */st->need_parsing = AVSTREAM_PARSE_NONE;} else if (st->need_parsing == AVSTREAM_PARSE_HEADERS)st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;else if (st->need_parsing == AVSTREAM_PARSE_FULL_ONCE)st->parser->flags |= PARSER_FLAG_ONCE;else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW)st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;}// 不需要parse的情况下,packet还存放在pkt变量中,没有放在parse_queue,所以got_packet = 1if (!st->need_parsing || !st->parser) {/* no parsing needed: we just output the packet as is */compute_pkt_fields(s, st, NULL, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE);if ((s->iformat->flags & AVFMT_GENERIC_INDEX) &&(pkt->flags & AV_PKT_FLAG_KEY) && pkt->dts != AV_NOPTS_VALUE) {ff_reduce_index(s, st->index);av_add_index_entry(st, pkt->pos, pkt->dts,0, 0, AVINDEX_KEYFRAME);}got_packet = 1;} else if (st->discard < AVDISCARD_ALL) {if ((ret = parse_packet(s, pkt, pkt->stream_index, 0)) < 0)return ret;st->codecpar->sample_rate = st->internal->avctx->sample_rate;st->codecpar->bit_rate = st->internal->avctx->bit_rate;st->codecpar->channels = st->internal->avctx->channels;st->codecpar->channel_layout = st->internal->avctx->channel_layout;st->codecpar->codec_id = st->internal->avctx->codec_id;} else {/* free packet */av_packet_unref(pkt);}if (pkt->flags & AV_PKT_FLAG_KEY)st->internal->skip_to_keyframe = 0;if (st->internal->skip_to_keyframe) {av_packet_unref(pkt);got_packet = 0;}}//未找到,然后parse_queue中又存在的话,那就说明pkt被转到parse_queue中,所以从parse_queue中获取if (!got_packet && s->internal->parse_queue)ret = avpriv_packet_list_get(&s->internal->parse_queue, &s->internal->parse_queue_end, pkt);if (ret >= 0) {AVStream *st = s->streams[pkt->stream_index];int discard_padding = 0;if (st->internal->first_discard_sample && pkt->pts != AV_NOPTS_VALUE) {int64_t pts = pkt->pts - (is_relative(pkt->pts) ? RELATIVE_TS_BASE : 0);int64_t sample = ts_to_samples(st, pts);int duration = ts_to_samples(st, pkt->duration);int64_t end_sample = sample + duration;if (duration > 0 && end_sample >= st->internal->first_discard_sample &&sample < st->internal->last_discard_sample)discard_padding = FFMIN(end_sample - st->internal->first_discard_sample, duration);}if (st->internal->start_skip_samples && (pkt->pts == 0 || pkt->pts == RELATIVE_TS_BASE))st->internal->skip_samples = st->internal->start_skip_samples;if (st->internal->skip_samples || discard_padding) {uint8_t *p = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10);if (p) {AV_WL32(p, st->internal->skip_samples);AV_WL32(p + 4, discard_padding);av_log(s, AV_LOG_DEBUG, "demuxer injecting skip %d / discard %d\n", st->internal->skip_samples, discard_padding);}st->internal->skip_samples = 0;}if (st->internal->inject_global_side_data) {for (i = 0; i < st->nb_side_data; i++) {AVPacketSideData *src_sd = &st->side_data[i];uint8_t *dst_data;if (av_packet_get_side_data(pkt, src_sd->type, NULL))continue;dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size);if (!dst_data) {av_log(s, AV_LOG_WARNING, "Could not inject global side data\n");continue;}memcpy(dst_data, src_sd->data, src_sd->size);}st->internal->inject_global_side_data = 0;}}av_opt_get_dict_val(s, "metadata", AV_OPT_SEARCH_CHILDREN, &metadata);if (metadata) {s->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED;av_dict_copy(&s->metadata, metadata, 0);av_dict_free(&metadata);av_opt_set_dict_val(s, "metadata", NULL, AV_OPT_SEARCH_CHILDREN);}#if FF_API_LAVF_AVCTXupdate_stream_avctx(s);
#endifif (s->debug & FF_FDEBUG_TS)av_log(s, AV_LOG_DEBUG,"read_frame_internal stream=%d, pts=%s, dts=%s, ""size=%d, duration=%"PRId64", flags=%d\n",pkt->stream_index,av_ts2str(pkt->pts),av_ts2str(pkt->dts),pkt->size, pkt->duration, pkt->flags);/* A demuxer might have returned EOF because of an IO error, let's* propagate this back to the user. */if (ret == AVERROR_EOF && s->pb && s->pb->error < 0 && s->pb->error != AVERROR(EAGAIN))ret = s->pb->error;return ret;
}
ff_read_packet源码
从AVInputFormat或者raw_packet_buffer中读取一个AVPacket,如果raw_packet_buffer中有,就从raw_packet_buffer中读取,没有的话,只能从对应的AVInputFormat中读取。
int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{int ret, i, err;AVStream *st;#if FF_API_INIT_PACKET
FF_DISABLE_DEPRECATION_WARNINGSpkt->data = NULL;pkt->size = 0;av_init_packet(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#elseav_packet_unref(pkt);
#endiffor (;;) {PacketList *pktl = s->internal->raw_packet_buffer;const AVPacket *pkt1;// probe的时候是不是有缓存在内存中的packetif (pktl) {st = s->streams[pktl->pkt.stream_index];if (s->internal->raw_packet_buffer_remaining_size <= 0)if ((err = probe_codec(s, st, NULL)) < 0)return err;if (st->internal->request_probe <= 0) {avpriv_packet_list_get(&s->internal->raw_packet_buffer,&s->internal->raw_packet_buffer_end, pkt);s->internal->raw_packet_buffer_remaining_size += pkt->size;return 0;}}// 从对应的AVInputFormat格式中读取AVPacket, 在Mp4格式中就是读取Sample。ret = s->iformat->read_packet(s, pkt);if (ret < 0) {av_packet_unref(pkt);/* Some demuxers return FFERROR_REDO when they consumedata and discard it (ignored streams, junk, extradata).We must re-call the demuxer to get the real packet. */if (ret == FFERROR_REDO)continue;if (!pktl || ret == AVERROR(EAGAIN))return ret;for (i = 0; i < s->nb_streams; i++) {st = s->streams[i];if (st->probe_packets || st->internal->request_probe > 0)if ((err = probe_codec(s, st, NULL)) < 0)return err;av_assert0(st->internal->request_probe <= 0);}continue;}// 把数据从 pkt->buffer->data 转移到 pkt->dataerr = av_packet_make_refcounted(pkt);if (err < 0) {av_packet_unref(pkt);return err;}if (pkt->flags & AV_PKT_FLAG_CORRUPT) {av_log(s, AV_LOG_WARNING,"Packet corrupt (stream = %d, dts = %s)",pkt->stream_index, av_ts2str(pkt->dts));if (s->flags & AVFMT_FLAG_DISCARD_CORRUPT) {av_log(s, AV_LOG_WARNING, ", dropping it.\n");av_packet_unref(pkt);continue;}av_log(s, AV_LOG_WARNING, ".\n");}av_assert0(pkt->stream_index < (unsigned)s->nb_streams &&"Invalid stream index.\n");st = s->streams[pkt->stream_index];// 更新AVProgram中的pts_wrap_reference和pts_wrap_behavior// 不过Mp4格式并不用这个AVProgramif (update_wrap_reference(s, st, pkt->stream_index, pkt) && st->internal->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) {// correct first time stamps to negative valuesif (!is_relative(st->first_dts))st->first_dts = wrap_timestamp(st, st->first_dts);if (!is_relative(st->start_time))st->start_time = wrap_timestamp(st, st->start_time);if (!is_relative(st->cur_dts))st->cur_dts = wrap_timestamp(st, st->cur_dts);}// 如果有wrap的行为,就根据pts_wrap_reference和pts_wrap_behavior来重新定义pts和dtspkt->dts = wrap_timestamp(st, pkt->dts);pkt->pts = wrap_timestamp(st, pkt->pts);force_codec_ids(s, st);/* TODO: audio: time filter; video: frame reordering (pts != dts) */if (s->use_wallclock_as_timestamps)pkt->dts = pkt->pts = av_rescale_q(av_gettime(), AV_TIME_BASE_Q, st->time_base);// 用的不是raw_packet_buffer中的AVPacket就在这里returnif (!pktl && st->internal->request_probe <= 0)return ret;err = avpriv_packet_list_put(&s->internal->raw_packet_buffer,&s->internal->raw_packet_buffer_end,pkt, NULL, 0);if (err < 0) {av_packet_unref(pkt);return err;}pkt1 = &s->internal->raw_packet_buffer_end->pkt;s->internal->raw_packet_buffer_remaining_size -= pkt1->size;if ((err = probe_codec(s, st, pkt1)) < 0)return err;}
}相关文章:
从头用脚分析FFmpeg源码 - av_read_frame
av_read_frame作用 /*** Return the next frame of a stream.* This function returns what is stored in the file, and does not validate* that what is there are valid frames for the decoder. It will split what is* stored in the file into frames and return one f…...
第17章_触发器
第17章_触发器 🏠个人主页:shark-Gao 🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉 🎉目前状况:23届毕业生,目前在某公…...
3956. 截断数组
3956. 截断数组 - AcWing题库 3956. 截断数组 【题目描述】 给定一个长度为 nn 的数组 a1,a2,…,ana1,a2,…,an。 现在,要将该数组从中间截断,得到三个非空子数组。 要求,三个子数组内各元素之和都相等。 请问,共有多少种不同…...
React Labs: 我们最近在做什么——2023 年 3 月
原文:https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023 React Server Components React Server Components(下文简称 RSC) 是由 React 团队设计的新应用程序架构。 我们首先在一个介绍性演讲和一个RFC中分享了我们对 RSC 的…...
文件系统设计详解
抽象的文件系统以目录的形式来组织文件,我们可以利用该文件系统来读取某个文件的内容,也可以对目录或者文件实施监控并及时获取变化的通知。 IChangeToken IChangeToken对象就是一个与某组监控数据相关联的“令牌”(Token)&#x…...
好看~立马启动python实现美女通通下
人生苦短,我用python一、环境版本使用二、代码实现思路三、代码展示:导入模块伪装(请求头)四、部分好看截图,更多的就自己去采集噜~吃饭放松的时候哇一不小心看见了很多好看的东西 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 独乐乐不如众乐乐…...
Git 安装设置
1、安装 安装以下三个软件: Git-2.13.3-64-bit.exe TortoiseGit-2.4.0.2-64bit.msi TortoiseGit-LanguagePack-2.4.0.0-64bit-zh_CN.msi 安装过程中不用填写、不用选择,全部点"下一步",完成后需要重启机器。 2、基本设…...
Python-闭包
介绍 Python的闭包是一种高级的编程技巧,它可以在函数内部定义另一个函数,并返回该函数的引用。这个内部函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕 好处 1)闭包可以避免全局变量的污染,使得代码更…...
Gitlab中Pipeline语法四
Gitlab中Pipeline语法 cache cache:paths 在job build中定义缓存,将会缓存target目录下的所有*.jar文件当全局定义了cache:paths会被job中覆盖.以下实例将缓存target目录 buld:script: buildcache:paths:- target/*.jar#设置key可以解决cache被覆盖 cache:paths:- my/files…...
Go语言精修(尚硅谷笔记)第五章
五、程序流程控制 5.1 单分支控制 package main import ("fmt" )func main() {//请大家看个案例[ifDemo.go]://编写一个程序,可以输入人的年龄,如果该同志的年龄大于18岁,则输出 "你年龄大//于18,要对自己的行为负责!"//分析 //1.年龄 > var age int…...
三、MySQL 高级(DML 增删改)
三、MySQL 高级(DML 增删改) 3.1 DML 数据操纵语言 DML(Data Manipulation Language)DML对数据库中表记录的执行操作 插入(INSERT) 插入单行数据 插入多行数据 将查询结果插入到新表 更新(…...
面向AI编程的本质是什么?
面向AI编程的本质是什么? 面向AI编程的本质是编程的第五代编程语言,与自然语言非常相似,但是是有区别的。 因此出现了针对与AI通话的提示工程。 简单地回顾一下编程语言的发展史, 第一代编程语言是机器语言,它直接使…...
深入浅出——深度学习训练中的warmup
❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…...
你知道如何用C语言将格式化数据和字符串相互转换吗?
今天重点介绍2个函数,分别是sprintf和sscanf,用来将格式化数据和字符串相互转换。它们的作用分别是: sprintf函数用于将格式化数据转换成字符串。sscanf函数用于将字符串转换成格式化数据。 接下来是第一个大问题:我怎么记忆呢&…...
免费一键生成原创文章-原创文章批量生成
免费使用一键生成原创文章,轻松解决写作难题! 您是否因为写作枯竭、文章档次不高,而感到烦恼?现在,我们有一个免费的文章创作工具,帮助您无需付出太多的努力就能高效地创造原创文章。 一键生成࿱…...
【数据库管理】④重做日志Redo Log
1. Redo log(重做日志)的功能 重做日志(Redo log)是数据库管理系统中的一种机制,主要作用包括: 提供事务的持久性支持:重做日志记录了每个事务对数据库所做的修改操作,以便在系统故障或崩溃时,通…...
5-python文件操作
文章目录1.打开文件2.文件读取3.文件关闭4.文件写入/追加1.打开文件 当传参顺序不一致时,不能使用位置传参,应使用关键字传参 open(file, mode‘r’, buffering-1, encodingNone, errorsNone, newlineNone, closefdTrue, openerNone) 通常使用…...
企业级Oracle入门Linux/Unix基础①
1、了解计算机系统的组成、操作系统介绍、IT技术发展与云计算、服务器的分类、存储设备介绍、常用的主机存储有哪些? 1.1 计算机系统的组成: 计算机系统由硬件和软件两部分组成。硬件包括中央处理器(CPU)、内存、输入输出设备、…...
NexNoSQL Client:Elasticsearch、Redis、MongoDB三合一的可视化客户端管理工具
背景: 工作中我们使用了Elasticsearch作为存储,来支持内容的搜索,Elasticsearch这个软件大家都耳熟能详,它是一个分布式、高扩展、高实时的搜索与数据分析引擎,不仅仅支持文本索引,还支持聚合操作…...
如果大学能重来,我绝对能吊打90%的大学生,早知道这方法就好了
最近收到很多大学生粉丝的私信,大多数粉丝们都迷茫着大学计算机该怎么学,毕业后才能找到好工作。 可能是最近回答这方面的问题有点多,昨晚还真梦回大学…其实工作了20多年,当过高管,创过业,就差没写书了。…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
