Android audio_policy_configuration.xml加载流程

目录

一、audio_policy_configuration.xml文件被加载流程

1、AudioPolicyService 创建阶段

2、createAudioPolicyManager 实现

3、AudioPolicyManager 构造

4、配置文件解析 loadConfig

5、核心解析逻辑 PolicySerializer::deserialize

二、AudioPolicyConfig类解析

1、AudioPolicyConfig 类定义

2、module标签

3、MixPort标签

4、DevicePort标签

5、route标签


一、audio_policy_configuration.xml文件被加载流程

1、AudioPolicyService 创建阶段

文件路径:frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp

void AudioPolicyService::onFirstRef() {mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
}

系统服务启动后,调用 createAudioPolicyManager,创建 AudioPolicyManager 对象。

2、createAudioPolicyManager 实现

extern "C" AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface) {AudioPolicyManager *apm = new AudioPolicyManager(clientInterface);status_t status = apm->initialize();if (status != NO_ERROR) {delete apm;apm = nullptr;}return apm;
}

作用

  • 创建 AudioPolicyManager

  • 立即调用 initialize() 初始化流程;

  • 如果失败,释放资源

3、AudioPolicyManager 构造

文件
frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp


AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface): AudioPolicyManager(clientInterface, false /*forTesting*/)
{loadConfig();
}void AudioPolicyManager::loadConfig() {if (deserializeAudioPolicyXmlConfig(getConfig()) != NO_ERROR) {ALOGE("could not load audio policy configuration file, setting defaults");getConfig().setDefault();}
}

构造时直接调用 loadConfig(),开始读取音频配置文件。

4、配置文件解析 loadConfig

文件
frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

status_t deserializeAudioPolicyFile(const char *fileName, AudioPolicyConfig *config) {PolicySerializer serializer;return serializer.deserialize(fileName, config);
}

调用 PolicySerializer::deserialize,使用 libxml2 解析音频 XML 配置文件。

5、核心解析逻辑 PolicySerializer::deserialize

status_t PolicySerializer::deserialize(const char *configFile, AudioPolicyConfig *config)
{// 使用智能指针包装 libxml2 的解析接口,解析传入的 XML 文件auto doc = make_xmlUnique(xmlParseFile(configFile));if (doc == nullptr) {// 如果解析失败,打印错误并返回 BAD_VALUEALOGE("%s: Could not parse %s document.", __func__, configFile);return BAD_VALUE;}// 获取 XML 文档的根节点xmlNodePtr root = xmlDocGetRootElement(doc.get());if (root == NULL) {// 如果根节点为空,打印错误并返回 BAD_VALUEALOGE("%s: Could not parse %s document: empty.", __func__, configFile);return BAD_VALUE;}// 处理 XInclude(XML文件可以引用其他XML文件的机制)if (xmlXIncludeProcess(doc.get()) < 0) {// 如果包含文件处理失败,记录错误,但不直接返回ALOGE("%s: libxml failed to resolve XIncludes on %s document.", __func__, configFile);}// 检查根节点名称是否符合预期if (xmlStrcmp(root->name, reinterpret_cast<const xmlChar*>(rootName)))  {ALOGE("%s: No %s root element found in xml data %s.", __func__, rootName,reinterpret_cast<const char*>(root->name));return BAD_VALUE;}// 获取根节点的 version 属性std::string version = getXmlAttribute(root, versionAttribute);if (version.empty()) {// 如果 version 属性不存在,返回错误ALOGE("%s: No version found in root node %s", __func__, rootName);return BAD_VALUE;}// 验证版本是否与期望版本一致if (version != mVersion) {ALOGE("%s: Version does not match; expect %s got %s", __func__, mVersion.c_str(),version.c_str());return BAD_VALUE;}// 开始解析子节点// Step 1: 解析 Module 列表ModuleTraits::Collection modules;status_t status = deserializeCollection<ModuleTraits>(root, &modules, config);if (status != NO_ERROR) {// 如果模块解析失败,直接返回错误状态return status;}// 将解析到的模块设置到 config 对象中config->setHwModules(modules);// Step 2: 解析全局配置GlobalConfigTraits::deseria

功能拆解

  • xmlParseFile:加载并解析 XML 文件。

  • xmlDocGetRootElement:获取根节点,确保结构有效。

  • xmlXIncludeProcess:处理 XInclude,允许配置文件嵌套引用子文件。

  • 验证 root 名称、版本号。

如果通过校验,进入内容解析:

deserializeCollection<ModuleTraits>(root, &modules, config);
config->setHwModules(modules);
GlobalConfigTraits::deserialize(root, config);
SurroundSoundTraits::deserialize(root, config);

经过以上代码之后,最终audio_policy_configuration.xml配置文件会转化为以下的C++类AudioPolicyConfig。

二、AudioPolicyConfig类解析

audio_policy_configuration.xml配置文件配置了Android Audio的设备、流以及设备和流之间的路由等相关信息。写明了Android Audio支持哪些设备、哪些流以及它们支持的编码格式、采样率等信息。文件大致内容如下。

1、AudioPolicyConfig 类定义


class AudioPolicyConfig {static const constexpr char* const kDefaultEngineLibraryNameSuffix = "default";std::string mSource; //为config字符串目录,一般在odm/etc、/vendor/etc、/system/etc下的audio_policy_configuration.xmlstd::string mEngineLibraryNameSuffix;HwModuleCollection &mHwModules; //保存了配置文件中所有的所有module标签集合,每个module标签对应一个HwModule类DeviceVector &mOutputDevices; //attchedDevices标签中,设备名称名字和devicePort标签的tagName相同,且type中有OUT字眼的DeviceDescriptor实体类集合DeviceVector &mInputDevices; //attchedDevices标签中,设备名称名字和devicePort标签的tagName相同,且type中有in字眼的DeviceDescriptor实体类集合sp<DeviceDescriptor> &mDefaultOutputDevice; //保存defaultOutputDevice标签内名字和devicePort标签的tagName相同// TODO: remove when legacy conf file is removed. true on devices that use DRC on the// DEVICE_CATEGORY_SPEAKER path to boost soft sounds, used to adjust volume curves accordingly.// Note: remove also speaker_drc_enabled from global configuration of XML config file.bool mIsSpeakerDrcEnabled;bool mIsCallScreenModeSupported;SurroundFormats mSurroundFormats;
};
<modules><!-- Primary Audio HAL --><module name="primary" halVersion="3.0"><attachedDevices><item>Speaker</item><item>Built-In Mic</item><item>Built-In Back Mic</item></attachedDevices><defaultOutputDevice>Speaker</defaultOutputDevice><mixPorts>……</mixPorts><devicePorts>……</devicePorts><!-- route declaration, i.e. list all available sources for a given sink --><routes>……</routes></module><!-- A2dp Input Audio HAL --><xi:include href="a2dp_in_audio_policy_configuration.xml"/>……</modules><!-- End of Modules section -->

2、module标签

每个module标签对应着相应的hal层实现,如primary、usb、a2dp等

<modules><!-- Primary Audio HAL --><module name="primary" halVersion="3.0"><attachedDevices><item>Speaker</item><item>Built-In Mic</item><item>Built-In Back Mic</item></attachedDevices>……</module><!-- A2dp Input Audio HAL --><xi:include href="a2dp_in_audio_policy_configuration.xml"/>……</modules><!-- End of Modules section -->

module标签对应C++实体类HWModule。

class HwModule {const String8 mName; // hal层模块对应的module名字(primary, a2dp ...)audio_module_handle_t mHandle;OutputProfileCollection mOutputProfiles; // mixport标签role为source类型,对应IOProfle实体类集合InputProfileCollection mInputProfiles;  // mixport标签role为sink的类型,对应IOProfle实体类集合uint32_t mHalVersion; // hal层模块的版本信息DeviceVector mDeclaredDevices; // 所有的deviceport标签,对应DeviceDescriptor实体类的集合DeviceVector mDynamicDevices; /**< devices that can be added/removed at runtime (e.g. rsbumix)*/AudioRouteVector mRoutes; // 所有的routePolicyAudioPortVector mPorts; // 所有的mixport,deviceport标签对应的实体类,因为IOProfle和DeviceDescriptor都继承了AudioPort,所以相当于这是一个AudioPort集合
};

3、MixPort标签

mixport标签可以理解为stream流,配置了相应的格式、采样率以及mask,且分为输出、输入流。一个mixport标签可能有多个profile属性,也就是支持很多编码格式等属性。

<mixPort name="compressed_offload" role="source"flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD|AUDIO_OUTPUT_FLAG_NON_BLOCKING"><profile name="" format="AUDIO_FORMAT_MP3"samplingRates="8000,11025,12000,16000,22050,24000,32000,44100,48000"channelMasks="AUDIO_CHANNEL_OUT_STEREO,AUDIO_CHANNEL_OUT_MONO"/><profile name="" format="AUDIO_FORMAT_AAC"samplingRates="8000,11025,12000,16000,22050,24000,32000,44100,48000"channelMasks="AUDIO_CHANNEL_OUT_STEREO,AUDIO_CHANNEL_OUT_MONO"/><profile name="" format="AUDIO_FORMAT_AAC_LC"samplingRates="8000,11025,12000,16000,22050,24000,32000,44100,48000"channelMasks="AUDIO_CHANNEL_OUT_STEREO,AUDIO_CHANNEL_OUT_MONO"/>
</mixPort>

一个mixport标签对应一个IOProfile实体类。

class IOProfile : public AudioPort, public PolicyAudioPort {// 可以同时打开的流的最大数量,默认为1。赋值为0时,表示无穷大。uint32_t     maxOpenCount;// 目前已打开的流的数量。uint32_t     curOpenCount;// 同时处于活跃状态的流的最大数量,默认为1。赋值为0时,表示无穷大。uint32_t     maxActiveCount;// 正处于活跃状态的流的数量。 针对于Hal层的流而言。uint32_t     curActiveCount;private:/** 当前流支持的设备集合;* 如果是sink输入流,查找规则如下:* 1. 遍历其父类的成员mRoutes,因为是输入流,所以遍历mRoute集合中sink为自己的route,也就是找有哪些源source设备把数据传给自己。* 2. 找到route后,根据route中source保存的对象,且对象type是AUDIO_PORT_TYPE_DEVICE类型(就是devicesPort标签对应的实体类DeviceDescriptor)* 3. 把DeviceDescriptor保存在集合中,保存在以下mSupportedDevices中,作为其支持的设备;* 输出流,同理;最终的结果就是:* 作为输出流source,mSupportedDevices保存此流可以输出到对应的device,stream -> device* 作为输入流sink,mSupportedDevices保存了其他device能输出数据到此流,  device -> stream**/DeviceVector mSupportedDevices;
};class AudioPort : public virtual RefBase, public virtual Parcelable
{AudioGains mGains; // gain controllers
protected:std::string  mName; // 对应mixport的nameaudio_port_type_t mType; // AUDIO_PORT_TYPE_MIX (此处固定)audio_port_role_t mRole; // AUDIO_PORT_ROLE_SOURCE/AUDIO_PORT_ROLE_SINK(由mixport的role决定)AudioProfileVector mProfiles; // AudioProfile的集合,对应mixport里面的多个profile// Audio capabilities that are defined by hardware descriptors when the format is unrecognized// by the platform, e.g. short audio descriptor in EDID for HDMI.std::vector<media::ExtraAudioDescriptor> mExtraAudioDescriptors;
};class PolicyAudioPort : public virtual RefBase, private HandleGenerator<audio_port_handle_t>
{uint32_t mFlags; // attribute flags mask (e.g primary output, direct output...).sp<HwModule> mModule;     // 通过attach函数与HwModule绑定AudioRouteVector mRoutes; // 相关连的route标签集合
};

mixport内部的Profile标签对应的C++类AudioProfile 如下。在解析以上标签至profile时,会单独创建AudioProfile。

class AudioProfile final : public RefBase, public Parcelable
{std::string  mName; // profile的name// 以下三个变量对应配置文件中profile中的置,由初始化时进行赋值audio_format_t mFormat; // The format for an audio profile should only be set when initialized.ChannelMaskSet mChannelMasks;SampleRateSet mSamplingRates;// 以下三个对应上面三位,如果三位都有值,则为false固定的,如果xml没有指定值,则为true表示是动态的值bool mIsDynamicFormat = false;bool mIsDynamicChannels = false;bool mIsDynamicRate = false;audio_encapsulation_type_t mEncapsulationType = AUDIO_ENCAPSULATION_TYPE_NONE;AudioProfile() = default;AudioProfile& operator=(const AudioProfile& other);
};

4、DevicePort标签

devicePort标签可以理解为一个device设备,设备也分output和input,以type中的关键字“IN”和“OUT”进行区分。

<devicePort tagName="Speaker" role="sink" type="AUDIO_DEVICE_OUT_SPEAKER" address=""><profile name="" format="AUDIO_FORMAT_PCM_16_BIT"samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/><gains><gain name="gain_1" mode="AUDIO_GAIN_MODE_JOINT"minValueMB="-8400"maxValueMB="4000"defaultValueMB="0"stepValueMB="100"/></gains>
</devicePort>

一个devicePort对应一个C++类DeviceDescriptor。

class DeviceDescriptor : public DeviceDescriptorBase,public PolicyAudioPort, public PolicyAudioPortConfig
{std::string mTagName; // 对应的tagName字段FormatVector        mEncodedFormats; // encodedFormats转换的枚举值audio_format_t      mCurrentEncodedFormat;bool                mIsDynamic = false;const std::string   mDeclaredAddress; // address字段对应的地址
};class DeviceDescriptorBase : public AudioPort, public AudioPortConfig
{AudioDeviceTypeAddr mDeviceTypeAddr;uint32_t mEncapsulationModes = 0;uint32_t mEncapsulationMetadataTypes = 0;
};class AudioPort : public virtual RefBase, public virtual Parcelable
{AudioGains mGains; // gain controllers
protected:std::string  mName; // 对应devicePort的nameaudio_port_type_t mType; // AUDIO_PORT_TYPE_DEVICE(此处固定)audio_port_role_t mRole; // AUDIO_PORT_ROLE_SOURCE/AUDIO_PORT_ROLE_SINK(由mixport的role决定)AudioProfileVector mProfiles; // AudioProfile的集合,对应devicePort里面的多个profile// Audio capabilities that are defined by hardware descriptors when the format is unrecognized// by the platform, e.g. short audio descriptor in EDID for HDMI.std::vector<media::ExtraAudioDescriptor> mExtraAudioDescriptors;
};

同MixPort一样,devicePort也会解析内部的profile标签,创建新的AudioProfile。

class AudioProfile final : public RefBase, public Parcelable
{std::string  mName; // profile的name// 以下三个变量对应配置文件中profile中的置,由初始化时进行赋值audio_format_t mFormat; // The format for an audio profile should only be set when initialized.ChannelMaskSet mChannelMasks;SampleRateSet mSamplingRates;// 以下三个对应上面三位,如果三位都有值,则为false固定的,如果xml没有指定值,则为true表示是动态的值bool mIsDynamicFormat = false;bool mIsDynamicChannels = false;bool mIsDynamicRate = false;audio_encapsulation_type_t mEncapsulationType = AUDIO_ENCAPSULATION_TYPE_NONE;AudioProfile() = default;AudioProfile& operator=(const AudioProfile& other);
};

5、route标签

route是把deviceport和mixport连接起来的路由,数据由一个stream输出到另一个device,或者从一个device输出到另一个stream。

<route type="mix" sink="Speaker"sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>

route标签对应C++的AudioRoute类。

class AudioRoute  : public virtual RefBase
{PolicyAudioPortVector mSources; //所有的deviceport、mixport标签转化的实体类都保存到HwModule的mPorts成员了,所以是用name去mPorts里面查找,只是source可能是多个,这里用集合保存sp<PolicyAudioPort> mSink; //同上audio_route_type_t mType; //AUDIO_ROUTE_MIX/AUDIO_ROUTE_MUX:根据type而定是互斥还是可融合
};

在Primary的module配置文件中通过如下语句去配置a2dp、usb的module。

<!-- A2dp Input Audio HAL --><xi:include href="a2dp_in_audio_policy_configuration.xml"/>

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

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

相关文章

使用 Docker 安装 Elastic Stack 并重置本地密码

Elastic Stack&#xff08;也被称为 ELK Stack&#xff09;是一个非常强大的工具套件&#xff0c;用于实时搜索、分析和可视化大量数据。Elastic Stack 包括 Elasticsearch、Logstash、Kibana 等组件。本文将展示如何使用 Docker 安装 Elasticsearch 并重置本地用户密码。 ###…

Unitest和pytest使用方法

unittest 是 Python 自带的单元测试框架&#xff0c;用于编写和运行可重复的测试用例。它的核心思想是通过断言&#xff08;assertions&#xff09;验证代码的行为是否符合预期。以下是 unittest 的基本使用方法&#xff1a; 1. 基本结构 1.1 创建测试类 继承 unittest.TestC…

git 版本提交规范

Git 提交规范&#xff08;Git Commit Message Convention&#xff09;是为了让项目的提交历史更加清晰、可读、便于追踪和自动化工具解析。常见的规范之一是 Conventional Commits&#xff0c;下面是一个推荐的格式规范&#xff1a; &#x1f31f; 提交信息格式&#xff08;Con…

stat判断路径

int stat(const char *pathname, struct stat *buf); pathname&#xff1a;用于指定一个需要查看属性的文件路径。 buf&#xff1a;struct stat 类型指针&#xff0c;用于指向一个 struct stat 结构体变量。调用 stat 函数的时候需要传入一个 struct stat 变量的指针&#xff0…

学习Docker遇到的问题

目录 1、拉取hello-world镜像报错 1. 检查网络连接 排查: 2. 配置 Docker 镜像加速器(推荐) 具体解决步骤: 1.在服务器上创建并修改配置文件,添加Docker镜像加速器地址: 2. 重启Docker 3. 拉取hello-world镜像 2、删除镜像出现异常 3、 容器内部不能运行ping命令 …

安宝特案例 | AR如何大幅提升IC封装厂检测效率?

前言&#xff1a;如何提升IC封装厂检测效率&#xff1f; 在现代电子产品的制造过程中&#xff0c;IC封装作为核心环节&#xff0c;涉及到复杂处理流程和严格质量检测。这是一家专注于IC封装的厂商&#xff0c;负责将来自IC制造商的晶圆进行保护、散热和导通处理。整个制程繁琐…

【Linux网络与网络编程】07.应用层协议HTTPS

HTTP 协议内容都是按照文本的方式明文传输的&#xff0c;这就导致在传输过程中出现一些被篡改的情况。HTTPS 就是在 HTTP 协议的基础上引入了一个加密层的应用层协议。 1. 基础概念 1.1 加密与解密 加密就是把明文&#xff08;要传输的信息&#xff09;进行一系列变换&#x…

【k8s】PV,PVC的回收策略——return、recycle、delete

PV 和 PVC 的回收策略主要用于管理存储资源的生命周期&#xff0c;特别是当 PVC 被删除时&#xff0c;PV 的处理方式。回收策略决定了 PV 在 PVC 被删除后的行为。 回收策略的类型 Kubernetes 提供了三种主要的回收策略&#xff0c;用于管理 PV 的生命周期&#xff1a; Reta…

2023蓝帽杯初赛内存取证-2

直接使用mimikatz插件来获取用户密码&#xff1a; vol.py --plugin/opt/volatility/plugins -f memdump.mem --profile Win7SP1x64 mimikatz 答案&#xff1a;3w.qax.com

使用dompurify修复XSS跨站脚本缺陷

1. 问题描述 漏洞扫描说有一个低危漏洞&#xff0c;容易被跨站脚本攻击XSS。 2. 使用dompurify修复 DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. 简单来说&#xff0c;我们可以使用 dompurify 处理xss跨站脚本攻击。 2.…

【c语言】指针和数组笔试题解析

一维数组: //数组名a如果既不单独放在sizeof()中&#xff0c;也不与&结合&#xff0c;那么就表示数组首元素的大小 //a一般表示数组首元素地址&#xff0c;只有两种情况表示整个数组&#xff0c;sizeof(arr)表示整个数组的大小&#xff0c;&arr表示数组的地址 int a[]…

机器人进阶---视觉算法(六)傅里叶变换在图像处理中怎么用

傅里叶变换在图像处理中怎么用 傅里叶变换的基本原理应用场景Python代码示例逐行解释总结傅里叶变换在图像处理中是一种重要的工具,它将图像从空间域转换到频域,从而可以对图像的频率特性进行分析和处理。傅里叶变换在图像滤波、图像增强、图像压缩和图像分析等方面都有广泛应…

深度学习与总结JVM专辑(七):垃圾回收器—CMS(图文+代码)

CMS垃圾收集器深度解析教程 1. 前言&#xff1a;为什么需要CMS&#xff1f;2. CMS 工作原理&#xff1a;一场与时间的赛跑2.1. 初始标记&#xff08;Initial Mark&#xff09;2.2. 并发标记&#xff08;Concurrent Mark&#xff09;2.3. 重新标记&#xff08;Remark&#xff09…

数据采集:AI 发展的基石与驱动力

人工智能&#xff08;AI&#xff09;无疑是最具变革性的技术力量之一&#xff0c;正以惊人的速度重塑着各行各业的格局。从智能语音助手到自动驾驶汽车&#xff0c;从精准的医疗诊断到个性化的推荐系统&#xff0c;AI 的广泛应用已深刻融入人们的日常生活与工作的各个层面。而在…

从信息泄露到内网控制

0x01 背景 之前常见用rce、文件上传等漏洞获取webshell&#xff0c;偶然遇到一次敏感信息泄露获取权限的渗透&#xff0c;简单记录一下过程。 0x02 信息泄露 发现系统某端口部署了minio服务&#xff0c;经过探测发现存在minio存储桶遍历 使用利用工具把泄露的文件全部整理一…

《门》凡是过往,皆为序曲。我们的爱,和最初一样

《门》凡是过往&#xff0c;皆为序曲。我们的爱&#xff0c;和最初一样 夏目漱石&#xff0c;本名夏目金之助&#xff0c;笔名漱石&#xff0c;日本近代作家&#xff0c;代表作有《三四郎》《门》《从此以后》《我是猫》《心》《明暗》等。 竺家荣 译 文章目录 《门》凡是过往&…

衡石ChatBI:依托开放架构构建技术驱动的差异化数据服务

在当今数字化浪潮中&#xff0c;企业对数据价值的挖掘和利用需求日益增长。BI&#xff08;商业智能&#xff09;工具作为企业获取数据洞察的关键手段&#xff0c;其技术架构的创新与发展至关重要。衡石科技的Chat BI凭借其独特的开放架构&#xff0c;在BI领域脱颖而出&#xff…

oracle中错误总结

oracle中给表起别名不能用as&#xff0c;用as报错 在 Oracle 数据库中&#xff0c;​​WITH 子句&#xff08;即 CTE&#xff0c;公共表表达式&#xff09;允许后续定义的子查询引用前面已经定义的 CTE​​&#xff0c;但 ​​前面的 CTE 无法引用后面的 CTE​​。这种设计类似…

NLP高频面试题(五十)——大模型(LLMs)分词(Tokenizer)详解

在自然语言处理(NLP)任务中,将文本转换为模型可处理的数字序列是必不可少的一步。这一步通常称为分词(tokenization),即把原始文本拆分成一个个词元(token)。对于**大型语言模型(LLM,Large Language Model,大型语言模型)**而言,选择合适的分词方案至关重要:分词的…

优化WAV音频文件

优化 WAV 音频文件通常涉及 减小文件体积、提升音质 或 适配特定用途&#xff08;如流媒体、广播等&#xff09;。以下是分场景的优化方法&#xff0c;涵盖工具和操作步骤&#xff1a; 一、减小文件体积&#xff08;无损/有损压缩&#xff09; 1. 无损压缩 转换格式&#xff1…