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

音视频入门基础:FLV专题(19)——FFmpeg源码中,解码Audio Tag的AudioTagHeader,并提取AUDIODATA的实现

一、引言

从《音视频入门基础:FLV专题(18)——Audio Tag简介》可以知道,未加密的情况下,FLV文件中的一个Audio Tag = Tag header + AudioTagHeader + AUDIODATA。本文讲述FFmpeg源码中是怎样解码Audio Tag的AudioTagHeader ,拿到里面的信息的,以及是怎样提取AUDIODATA的(以音频压缩编码格式为AAC为例)。

二、flv_read_packet函数

从《音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现》可以知道,FFmpeg源码中使用flv_read_packet函数来读取每个Tag的信息,该函数的前半部分实现了解码Tag header,获取其TagType属性的功能。然后根据TagType属性的值,判断该Tag为音频Tag、视频Tag还是脚本Tag。根据Tag的类型分别执行不同的解码操作:

    if (type == FLV_TAG_TYPE_AUDIO) {//...} else if (type == FLV_TAG_TYPE_VIDEO) {//...}else if (type == FLV_TAG_TYPE_META) {//...}else{//...}//...

如果在flv_read_packet函数的前半部分判断出该Tag为Audio Tag,flv_read_packet函数中会执行如下逻辑解码Audio Tag的AudioTagHeader:

    if (type == FLV_TAG_TYPE_AUDIO) {stream_type = FLV_STREAM_TYPE_AUDIO;flags    = avio_r8(s->pb);size--;} //...if (stream_type == FLV_STREAM_TYPE_AUDIO) {int bits_per_coded_sample;channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;bits_per_coded_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;if (!av_channel_layout_check(&st->codecpar->ch_layout) ||!st->codecpar->sample_rate ||!st->codecpar->bits_per_coded_sample) {av_channel_layout_default(&st->codecpar->ch_layout, channels);st->codecpar->sample_rate           = sample_rate;st->codecpar->bits_per_coded_sample = bits_per_coded_sample;}if (!st->codecpar->codec_id) {flv_set_audio_codec(s, st, st->codecpar,flags & FLV_AUDIO_CODECID_MASK);flv->last_sample_rate =sample_rate           = st->codecpar->sample_rate;flv->last_channels    =channels              = st->codecpar->ch_layout.nb_channels;} else {AVCodecParameters *par = avcodec_parameters_alloc();if (!par) {ret = AVERROR(ENOMEM);goto leave;}par->sample_rate = sample_rate;par->bits_per_coded_sample = bits_per_coded_sample;flv_set_audio_codec(s, st, par, flags & FLV_AUDIO_CODECID_MASK);sample_rate = par->sample_rate;avcodec_parameters_free(&par);}}//...if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 ||st->codecpar->codec_id == AV_CODEC_ID_MPEG4 ||st->codecpar->codec_id == AV_CODEC_ID_HEVC ||st->codecpar->codec_id == AV_CODEC_ID_AV1 ||st->codecpar->codec_id == AV_CODEC_ID_VP9) {int type = 0;if (enhanced_flv && stream_type == FLV_STREAM_TYPE_VIDEO) {type = flags & 0x0F;} else {type = avio_r8(s->pb);size--;}if (size < 0) {ret = AVERROR_INVALIDDATA;goto leave;}if (enhanced_flv && stream_type == FLV_STREAM_TYPE_VIDEO && flv->meta_color_info_flag) {flv_update_video_color_info(s, st); // update av packet side dataflv->meta_color_info_flag = 0;}if (st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_MPEG4 ||(st->codecpar->codec_id == AV_CODEC_ID_HEVC && type == PacketTypeCodedFrames)) {// sign extensionint32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;pts = av_sat_add64(dts, cts);if (cts < 0) { // dts might be wrongif (!flv->wrong_dts)av_log(s, AV_LOG_WARNING,"Negative cts, previous timestamps might be wrong.\n");flv->wrong_dts = 1;} else if (FFABS(dts - pts) > 1000*60*15) {av_log(s, AV_LOG_WARNING,"invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);dts = pts = AV_NOPTS_VALUE;}size -= 3;}if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_HEVC ||st->codecpar->codec_id == AV_CODEC_ID_AV1 || st->codecpar->codec_id == AV_CODEC_ID_VP9)) {AVDictionaryEntry *t;if (st->codecpar->extradata) {if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)return ret;ret = FFERROR_REDO;goto leave;}if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;/* Workaround for buggy Omnia A/XE encoder */t = av_dict_get(s->metadata, "Encoder", NULL, 0);if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))st->codecpar->extradata_size = 2;ret = FFERROR_REDO;goto leave;}}//...

下面我们分析上述代码块中解码Audio Tag的AudioTagHeader的原理。

三、flv_read_packet函数中解码Audio Tag的AudioTagHeader的实现

上述代码块中,首先通过avio_r8函数获取AudioTagHeader的第一个字节,也就是SoundFormat(占4位) + SoundRate(占2位) + SoundSize(占1位) + SoundType(占1位),存贮到局部变量flags中。关于avio_r8函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》:

    if (type == FLV_TAG_TYPE_AUDIO) {stream_type = FLV_STREAM_TYPE_AUDIO;flags    = avio_r8(s->pb);size--;} 

FLV文件相关的宏,定义在libavformat/flv.h中:

/* offsets for packed values */
#define FLV_AUDIO_SAMPLESSIZE_OFFSET 1
#define FLV_AUDIO_SAMPLERATE_OFFSET  2
#define FLV_AUDIO_CODECID_OFFSET     4#define FLV_VIDEO_FRAMETYPE_OFFSET   4/* bitmasks to isolate specific values */
#define FLV_AUDIO_CHANNEL_MASK    0x01
#define FLV_AUDIO_SAMPLESIZE_MASK 0x02
#define FLV_AUDIO_SAMPLERATE_MASK 0x0c
#define FLV_AUDIO_CODECID_MASK    0xf0

通过下面语句将AudioTagHeader的SoundType属性提取出来,转换得到音频声道数目。将频声道数目存贮到局部变量channels中:

        channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;

通过下面语句将AudioTagHeader的SoundRate属性提取出来,转换得到音频采样频率。将音频采样频率存贮到局部变量sample_rate中:

        sample_rate = 44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >>FLV_AUDIO_SAMPLERATE_OFFSET) >> 3;

通过下面语句将AudioTagHeader的SoundSize属性提取出来,转换得到Bit depth。将Bit depth存贮到局部变量bits_per_coded_sample中:

        bits_per_coded_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;

将上述得到的音频声道数目赋值给st->codecpar->ch_layout,将音频采样频率赋值给st->codecpar->sample_rate,将Bit depth赋值给st->codecpar->bits_per_coded_sample:

        if (!av_channel_layout_check(&st->codecpar->ch_layout) ||!st->codecpar->sample_rate ||!st->codecpar->bits_per_coded_sample) {av_channel_layout_default(&st->codecpar->ch_layout, channels);st->codecpar->sample_rate           = sample_rate;st->codecpar->bits_per_coded_sample = bits_per_coded_sample;}

将AudioTagHeader的SoundFormat属性提取出来,转换得到音频压缩编码格式。将音频压缩编码格式赋值给st->codecpar->codec_id中:

        if (!st->codecpar->codec_id) {flv_set_audio_codec(s, st, st->codecpar,flags & FLV_AUDIO_CODECID_MASK);//...}

由于st等价于s->streams[stream_index],stream_index为该音频流的流索引,指针s指向AVFormatContext变量。所以通过上面两步的赋值操作后,可以在flv_read_packet函数外部,通过(s->streams[stream_index])->codecpar拿到该音频的音频声道数目、音频采样频率、Bit depth、音频压缩编码格式。

当FLV文件中的音频为AAC格式时,AudioTagHeader包含AACPacketType属性。通过语句:type = avio_r8(s->pb) 获取AACPacketType属性:

    if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 ||st->codecpar->codec_id == AV_CODEC_ID_MPEG4 ||st->codecpar->codec_id == AV_CODEC_ID_HEVC ||st->codecpar->codec_id == AV_CODEC_ID_AV1 ||st->codecpar->codec_id == AV_CODEC_ID_VP9) {int type = 0;if (enhanced_flv && stream_type == FLV_STREAM_TYPE_VIDEO) {type = flags & 0x0F;} else {type = avio_r8(s->pb);size--;}
//...
}

至此,AudioTagHeader中的属性已被全部解析出来。然后flv_read_packet函数会继续往下执行,提取Audio Tag的AUDIODATA。

四、提取Audio Tag的AUDIODATA

从《音视频入门基础:FLV专题(18)——Audio Tag简介》可以知道,未加密的情况下,FLV文件中的一个Audio Tag = Tag header + AudioTagHeader + AUDIODATA。AUDIODATA为AudioTagBody。FLV文件的音频压缩编码格式为AAC时,AudioTagBody为AACAUDIODATA,当AACPacketType值为0时,AACAUDIODATA为AudioSpecificConfig;当AACPacketType值为1时,AACAUDIODATA包含一帧AAC音频压缩数据,所以下面得分情况讨论。

(一)AACPacketType的值为0

AACPacketType的值为0时,AACAUDIODATA为AudioSpecificConfig。flv_read_packet函数通过下面代码提取AudioSpecificConfig:

        if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_HEVC ||st->codecpar->codec_id == AV_CODEC_ID_AV1 || st->codecpar->codec_id == AV_CODEC_ID_VP9)) {AVDictionaryEntry *t;if (st->codecpar->extradata) {if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)return ret;ret = FFERROR_REDO;goto leave;}if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;/* Workaround for buggy Omnia A/XE encoder */t = av_dict_get(s->metadata, "Encoder", NULL, 0);if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))st->codecpar->extradata_size = 2;ret = FFERROR_REDO;goto leave;}

上面的代码块中,局部变量type存贮AudioTagHeader的AACPacketType属性。当AACPacketType值为0并且音频压缩编码格式为AAC并且还未获取AudioSpecificConfig时,会执行下面的代码块,从而拿到AudioSpecificConfig。下面代码块的作用是:读取该Audio Tag的AudioSpecificConfig,将其存贮到s->streams[stream_index]->codecpar->extradata指向的缓冲区中。其中stream_index为该路音频流在FLV文件中的流索引,size为AudioSpecificConfig所占的存贮空间(以字节为单位):

            if ((ret = flv_get_extradata(s, st, size)) < 0)return ret;

然后之后在flv_read_packet函数外部会通过decode_audio_specific_config_gb函数解码上述提取出来的AudioSpecificConfig,具体可以参考:《音视频入门基础:AAC专题(12)——FFmpeg源码中,解码AudioSpecificConfig的实现》。

(二)AACPacketType的值为1

当AACPacketType值为1时,AACAUDIODATA包含一帧AAC音频压缩数据。flv_read_packet函数通过下面代码提取AUDIODATA,即通过av_get_packet函数读取一帧AAC音频压缩数据,保存到pkt->data指向的缓冲区中。关于av_get_packet函数可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》。这样在执行下面的代码块后,pkt->data会得到该帧的实际的压缩后的AAC音频数据;pkt->dts会得到该帧的解码时间戳,解码时间戳来源于Tag header的Timestamp和TimestampExtended属性,具体可以参考:《音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现》;pkt->pts会得到该帧的显示时间戳,对于音频,显示时间戳等于解码时间戳:

    ret = av_get_packet(s->pb, pkt, size);if (ret < 0)return ret;pkt->dts          = dts;pkt->pts          = pts == AV_NOPTS_VALUE ? dts : pts;pkt->stream_index = st->index;pkt->pos          = pos;

相关文章:

音视频入门基础:FLV专题(19)——FFmpeg源码中,解码Audio Tag的AudioTagHeader,并提取AUDIODATA的实现

一、引言 从《音视频入门基础&#xff1a;FLV专题&#xff08;18&#xff09;——Audio Tag简介》可以知道&#xff0c;未加密的情况下&#xff0c;FLV文件中的一个Audio Tag Tag header AudioTagHeader AUDIODATA。本文讲述FFmpeg源码中是怎样解码Audio Tag的AudioTagHead…...

前端零基础入门到上班:【Day3】从零开始构建网页骨架HTML

HTML 基础入门&#xff1a;从零开始构建网页骨架 目录 1. 什么是 HTML&#xff1f;HTML 的核心作用 2. HTML 基本结构2.1 DOCTYPE 声明2.2 <html> 标签2.3 <head> 标签2.4 <body> 标签 3. HTML 常用标签详解3.1 标题标签3.2 段落和文本标签3.3 链接标签3.4 图…...

字符脱敏工具类

1、字符脱敏工具类 import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils;/*** 数据脱敏工具类** date 2024/10/30 13:44*/Slf4j public class DataDesensitizationUtils {public static final String STAR_1 "*";public static final …...

【jvm】jvm对象都分配在堆上吗

目录 1. 说明2. 堆上分配3. 栈上分配&#xff08;逃逸分析和标量替换&#xff09;4. 方法区分配5. 直接内存&#xff08;非堆内存&#xff09; 1. 说明 1.JVM的对象并不总是分配在堆上。2.堆是JVM用于存储对象实例的主要内存区域&#xff0c;存在一些特殊情况&#xff0c;对象…...

@AutoWired和 @Resource原理深度分析!

嗨&#xff0c;你好呀&#xff0c;我是猿java Autowired和Resource是 Java程序员经常用来实现依赖注入的两个注解&#xff0c;这篇文章&#xff0c;我们将详细分析这两个注解的工作原理、使用示例和它们之间的对比。 依赖注入概述 依赖注入是一种常见的设计模式&#xff0c;…...

C++设计模式创建型模式———原型模式

文章目录 一、引言二、原型模式三、总结 一、引言 与工厂模式相同&#xff0c;原型模式&#xff08;Prototype&#xff09;也是创建型模式。原型模式通过一个对象&#xff08;原型对象&#xff09;克隆出多个一模一样的对象。实际上&#xff0c;该模式与其说是一种设计模式&am…...

重学SpringBoot3-Spring WebFlux之SSE服务器发送事件

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ Spring WebFlux之SSE服务器发送事件 1. 什么是 SSE&#xff1f;2. Spring Boot 3 响应式编程与 SSE为什么选择响应式编程实现 SSE&#xff1f; 3. 实现 SSE 的基本步骤3.…...

YOLO即插即用模块---AgentAttention

Agent Attention: On the Integration of Softmax and Linear Attention 论文地址&#xff1a;https://arxiv.org/pdf/2312.08874 问题&#xff1a; 普遍使用的 Softmax 注意力机制在视觉 Transformer 模型中计算复杂度过高&#xff0c;限制了其在各种场景中的应用。 方法&a…...

探索开源语音识别的未来:高效利用先进的自动语音识别技术20241030

&#x1f680; 探索开源语音识别的未来&#xff1a;高效利用自动语音识别技术 &#x1f31f; 引言 在数字化时代&#xff0c;语音识别技术正在引领人机交互的新潮流&#xff0c;为各行业带来了颠覆性的改变。开源的自动语音识别&#xff08;ASR&#xff09;系统&#xff0c;如…...

学习路之TP6--workman安装

一、安装 首先通过 composer 安装 composer require topthink/think-worker 报错&#xff1a; 分析&#xff1a;最新版本需要TP8&#xff0c;或装低版本的 composer require topthink/think-worker:^3.*安装后&#xff0c; 增加目录 vendor\workerman vendor\topthink\think-w…...

.NET内网实战:通过白名单文件反序列化漏洞绕过UAC

01阅读须知 此文所节选自小报童《.NET 内网实战攻防》专栏&#xff0c;主要内容有.NET在各个内网渗透阶段与Windows系统交互的方式和技巧&#xff0c;对内网和后渗透感兴趣的朋友们可以订阅该电子报刊&#xff0c;解锁更多的报刊内容。 02基本介绍 03原理分析 在渗透测试和红…...

AI Agents - 自动化项目:计划、评估和分配

Agents: Role 角色Goal 目标Backstory 背景故事 Tasks&#xff1a; Description 描述Expected Output 期望输出Agent 代理 Automated Project: Planning, Estimation, and Allocation Initial Imports 1.本地文件helper.py # Add your utilities or helper functions to…...

Git的.gitignore文件

一、各语言对应的.gitignore模板文件 项目地址&#xff1a;https://github.com/github/gitignore 二、.gitignore文件不生效 .gitignore文件只是ignore没有被追踪的文件&#xff0c;已被追踪的文件&#xff0c;要先删除缓存文件。 # 单个文件 git rm --cached file/path/to…...

网站安全,WAF网站保护暴力破解

雷池的核心功能 通过过滤和监控 Web 应用与互联网之间的 HTTP 流量&#xff0c;功能包括&#xff1a; SQL 注入保护&#xff1a;防止恶意 SQL 代码的注入&#xff0c;保护网站数据安全。跨站脚本攻击 (XSS)&#xff1a;阻止攻击者在用户浏览器中执行恶意脚本。暴力破解防护&a…...

深度学习:梯度下降算法简介

梯度下降算法简介 梯度下降算法 我们思考这样一个问题&#xff0c;现在需要用一条直线来回归拟合这三个点&#xff0c;直线的方程是 y w ^ x b y \hat{w}x b yw^xb&#xff0c;我们假设斜率 w ^ \hat{w} w^是已知的&#xff0c;现在想要找到一个最好的截距 b b b。 一条…...

SparkSQL整合Hive后,如何启动hiveserver2服务

当spark sql与hive整合后&#xff0c;我们就无法启动hiveserver2的服务了&#xff0c;每次都要先启动hive的元数据服务&#xff08;nohup hive --service metastore&#xff09;才能启动hive,之前的beeline命令也用不了&#xff0c;hiveserver2的无法启动&#xff0c;这也导致我…...

前端路由如何从0开始配置?vue-router 的使用

在 Web 开发中&#xff0c;路由是指根据 URL 的不同部分将请求分发到不同的处理函数或页面的过程。路由是单页应用&#xff08;SPA, Single Page Application&#xff09;和服务器端渲染&#xff08;SSR, Server-Side Rendering&#xff09;应用中的一个重要概念。 在开发中如何…...

Java中的运算符【与C语言的区别】

目录 1. 算术运算符 1.0 赋值运算符&#xff1a; 1.1 四则运算符&#xff1a; - * / % 【取余与C有点不同】 1.2 增量运算符&#xff1a; - * / % * 【右侧运算结果会自动转换类型】 1.3 自增、自减&#xff1a;、-- 2. 关系运算符 3. 逻辑运算符 3.1 短路求值 3.2 【…...

二、基础语法

入门了解 注释 **作用&#xff1a;**在代码中加一些注释和说明&#xff0c;方便自己或者其他程序员阅读代码 两种格式&#xff1a; 单行注释&#xff1a;// 描述信息 通常放在一行代码的上方&#xff0c;或者一条语句的末尾&#xff0c;对该行代码进行说明 多行注释&#x…...

DB-GPT系列(一):DB-GPT能帮你做什么?

DB-GPT是一个开源的AI原生数据应用开发框架(AI Native Data App Development framework with AWEL and Agents)&#xff0c;围绕大模型提供灵活、可拓展的AI原生数据应用管理与开发能力&#xff0c;可以帮助企业快速构建、部署智能AI数据应用&#xff0c;通过智能数据分析、洞察…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...