从零自制docker-10-【cgroup进行容器资源限制】

news2024/11/18 2:33:39

文章目录

  • 目的
  • 导入包的相关公开原则
  • 当前进程的挂载信息
  • defer
  • `for scanner.Scan()`
  • 判断字符串包含
  • 新建的cgroup的默认文件
  • cpu相关配置
  • 对应到ubuntu 22.04版本的cpu相关配置
  • top
  • 注意
  • 查看你可使用的cpu
  • 注意
  • 启动后的top查看
  • 显示进程使用的cpu序号
  • 代码
  • 结果

目的

启动容器时通过-mem、-cpu 等 flag 相关命令行参数来实现容器 cpu、内存资源限制

导入包的相关公开原则

在Go语言中,导入包中的结构体(struct)和接口(interface)内的字段、方法也同样遵循导出规则:

  1. 结构体(struct):
    • 结构体内的字段如果是公开(导出)的,也需要以大写字母开头。公开的字段可以在包外部访问和修改。
    • 结构体内的方法(不论接收者是谁)如果是公开的,方法名也需要以大写字母开头,这样才能在包外部调用。
// 在 mypackage 包中
package mypackage

type ExportedStruct struct {
    PublicField string // 可以在其他包中访问
    privateField string // 不能在其他包中访问
}

func (s ExportedStruct) PublicMethod() {} // 可以在其他包中调用
func (s ExportedStruct) privateMethod() {} // 不能在其他包中调用
  1. 接口(interface):
    • 接口内的方法全部都是公开的,因为接口本身就是一种契约,定义了一组方法签名,无论接口本身还是其内的方法,都需要以大写字母开头才能在包外部看到和使用。
// 在 mypackage 包中
package mypackage

type ExportedInterface interface {
    PublicMethod() // 可以在其他包中实现和调用
}

总结:无论是结构体还是接口,其内部的公开字段和方法都需要以大写字母开头,以便在其他包中正常使用。对于接口而言,因为接口本身就是方法的集合,所以接口内的所有方法都会自动视为公开的。

当前进程的挂载信息

/proc/self/mountinfo 是Linux系统中的一个虚拟文件,位于 /proc 文件系统中,该文件提供了系统当前挂载点的详细信息,尤其是关于当前进程(self)的挂载点信息。

每一行内容都代表一个挂载点的详细描述,包括:

  1. 挂载ID(unique ID identifying the mount point across all mounts within the system)。
  2. 父挂载ID(the ID of the parent mount (or of self for the rootfs))。
  3. 主设备号和次设备号(major and minor numbers of the device mounted)。
  4. 挂载点路径(the mount point’s pathname)。
  5. 根目录的设备ID(root directory ID)。
  6. 挂载选项(options used when mounting the filesystem)。
  7. 文件系统类型(filesystem type)。
  8. 挂载源(where the filesystem was mounted from, e.g., device path or NFS server/share)。
  9. 特殊挂载标志(additional flags specific to the mount)。

通过读取 /proc/self/mountinfo 文件,可以详细了解当前进程所能看到的文件系统挂载情况,这对于系统管理和故障排查非常有用。例如,如果要检查某个目录挂载的文件系统类型、挂载来源、挂载选项等信息,可以查阅这个文件的内容。

defer

在Go语言中,defer f.Close() 是用来保证文件或资源一定会在函数执行完毕前被关闭的语句。这里的 f 是一个实现了 io.Closer 接口的对象,通常是一个文件句柄或其他需要关闭的资源对象。Close 方法用于关闭资源,释放系统资源。

defer 关键字在 Go 语言中用于延迟执行函数,即该函数会在包含它的函数执行结束(无论是正常返回还是通过 panic 导致的异常退出)前调用。defer 语句的执行顺序是 LIFO(后进先出),也就是说,最后被 defer 的函数会在其他 defer 函数之前被执行。

例如:

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close() // 当函数返回时,file.Close()会被调用,确保文件最终会被关闭

    // 对文件进行读写操作...

    return nil // 或者返回其他错误
}

在上面的代码中,无论 processFile 函数的执行是否成功,也不论函数是如何结束的(正常返回或抛出错误),file.Close() 都会在函数返回前被调用,确保了文件资源的正确释放。

for scanner.Scan()

在Go语言中,bufio.NewScanner(f) 创建了一个新的扫描器(Scanner),它可以从给定的输入流(在这里是 f)逐行读取数据。f 应该是一个实现了 io.Reader 接口的对象,如文件、网络连接或类似的数据流。

接着的 for scanner.Scan() 是一个循环,它会一直执行直到扫描器不再有可读的行为止:

  • scanner.Scan() 是一个方法调用,它尝试从输入流中读取下一行数据。如果成功读取到一行数据,则返回 true,并将读取到的数据存储在 scanner.Text()scanner.Bytes() 等方法可以访问的地方。
  • 如果 scanner.Scan() 读取到流的末尾,或者在读取过程中遇到了错误,它将返回 false 并且可以通过 scanner.Err() 检查发生的错误。

循环体内部就是对每一行数据进行处理的地方,例如:

f, err := os.Open("example.txt")
if err != nil {
    // 处理错误
}
defer f.Close()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
    line := scanner.Text() // 获取当前行的内容
    // 对line进行处理,如打印、解析或存入数据库等操作
    fmt.Println(line)
}

if err := scanner.Err(); err != nil {
    // 在循环结束后检查是否有读取错误
    fmt.Fprintln(os.Stderr, "Reading standard input:", err)
}

这样,for scanner.Scan() 循环就完成了一个从输入流中逐行读取数据并进行处理的过程。

判断字符串包含

在Go语言中,判断一个字符串(子串)是否存在于另一个字符串中,可以使用 strings.Contains 函数。以下是一个示例:

package main

import (
	"fmt"
	"strings"
)

func main() {
	// 主字符串
	mainStr := "Hello, world! This is a test string."

	// 要查找的子字符串
	subStr := "test"

	// 判断子字符串是否存在于主字符串中
	isFound := strings.Contains(mainStr, subStr)

	if isFound {
		fmt.Printf("Substring '%s' found in the main string.\n", subStr)
	} else {
		fmt.Printf("Substring '%s' not found in the main string.\n", subStr)
	}
}

在上述代码中,strings.Contains(mainStr, subStr) 会返回一个布尔值,如果 subStr 存在于 mainStr 中,则返回 true,否则返回 false

新建的cgroup的默认文件

在这里插入图片描述

  • cgroup.controllers:显示当前cgroup所使用的控制器。
  • cgroup.events:显示与cgroup相关的事件。
  • cgroup.freeze:用于冻结或解冻cgroup。
  • cgroup.kill:用于杀死cgroup中的所有进程。
  • cgroup.max.depth:显示cgroup的最大深度。
  • cgroup.max.descendants:显示cgroup的最大后代数。
  • cgroup.pressure:显示cgroup的压力状况。
  • cgroup.procs:显示属于cgroup的进程列表。
  • cgroup.stat:显示有关cgroup的统计信息。
  • cgroup.subtree_control:用于控制cgroup的子树。
  • cgroup.threads:显示属于cgroup的线程数。
  • cgroup.type:显示cgroup的类型。

接下来是与CPU和内存资源控制相关的文件:

  • cpuset.cpus:显示cpuset分配的CPU的列表。
  • cpuset.cpus.effective:显示实际生效的cpuset分配的CPU的列表。
  • cpuset.cpus.partition:显示cpuset的CPU分区。
  • cpuset.mems:显示cpuset分配的内存节点的列表。
  • cpuset.mems.effective:显示实际生效的cpuset分配的内存节点的列表。
  • cpu.idle:显示CPU处于空闲状态的时间。
  • cpu.max:显示CPU可用的最大频率。
  • cpu.stat:显示CPU的统计信息。
  • cpu.pressure:显示CPU的压力状况。
  • cpu.max.burst:显示CPU的最大突发频率。
  • cpu.uclamp.max:显示CPU的最大utilization clamp值。
  • cpu.uclamp.min:显示CPU的最小utilization clamp值。
  • cpu.weight:显示CPU权重。
  • cpu.weight.nice:显示CPU权重的“nice”值。
  • io.max:显示I/O最大限制。
  • io.pressure:显示I/O的压力状况。
  • io.prio.class:显示I/O的优先级类别。
  • io.stat:显示I/O的统计信息。

接着是与内存资源控制相关的文件:

  • memory.events:显示内存事件的统计信息。
  • memory.peak:显示内存使用量的峰值。
  • memory.swap.peak:显示交换空间使用量的峰值。
  • memory.events.local:显示本地内存事件的统计信息。
  • memory.pressure:显示内存的压力状况。
  • memory.reclaim:显示内存的回收情况。
  • memory.zswap.current:显示zswap的当前状态。
  • memory.high:显示内存的高阈值。
  • memory.low:显示内存的低阈值。
  • memory.max:显示内存的最大限制。
  • memory.min:显示内存的最小限制。
  • memory.numa_stat:显示NUMA节点的内存统计信息。
  • memory.oom.group:显示内存OOM的组信息。
  • memory.swap.current:显示交换空间的当前状态。
  • memory.swap.events:显示交换空间事件的统计信息。
  • memory.swap.high:显示交换空间的高阈值。
  • memory.swap.max:显示交换空间的最大限制。
  • memory.swap.events:显示交换空间事件的统计信息。

最后是与进程控制相关的文件:

  • pids.current:显示当前进程数。
  • pids.events:显示与进程相关的事件。
  • pids.max:显示最大进程数限制。
  • pids.peak:显示进程数的峰值。

cpu相关配置

让我们通过一些例子来解释 cpu.cfs_period_us、cpu.cfs_quota_us 和 cpu.shares 之间的关系以及它们如何共同控制 CPU 资源分配:

假设我们有一个系统,其中 CPU 时间可以分配给不同的控制组(cgroup):

  1. cpu.shares 示例

    • 假设我们有两个 cgroup,即“A”和“B”,并且我们希望“A”获得两倍于“B”的 CPU 时间。
    • 我们将“A”的 cpu.shares 设置为 1024,将“B”的 cpu.shares 设置为 512。
    • 在这种情况下,cgroup“A”将获得两倍于 cgroup“B”的 CPU 时间。如果“A”和“B”都在积极使用 CPU,则“A”将获得 2/3 的 CPU 时间,“B”将获得 1/3。
  2. cpu.cfs_period_us 和 cpu.cfs_quota_us 示例

    • 现在,让我们使用 cpu.cfs_period_us 和 cpu.cfs_quota_us 来实现更细粒度的控制。
    • 假设我们有一个 cgroup“C”,我们希望限制它在 1 秒内只能使用 500 毫秒的 CPU 时间。
    • 我们将“C”的 cpu.cfs_period_us 设置为 1,000,000(表示 1 秒),并将 cpu.cfs_quota_us 设置为 500,000(表示 500 毫秒)。
    • 在这种情况下,“C”中的进程在 1 秒的周期内将仅获得 500 毫秒的 CPU 时间。一旦它们达到配额,它们将在该周期的剩余时间内被暂停。
  3. 结合使用 cpu.shares、cpu.cfs_period_us 和 cpu.cfs_quota_us

    • 现在,让我们将这三个设置结合起来。

    • 假设我们有两个 cgroup,“D”和“E”,我们希望“D”获得三倍于“E”的 CPU 时间,但我们还希望限制“D”在 2 秒内只能使用 1 秒的 CPU 时间。

    • 我们将“D”的 cpu.shares 设置为 1536,将“E”的 cpu.shares 设置为 512。

    • 对于“D”,我们将 cpu.cfs_period_us 设置为 2,000,000(2 秒),并将 cpu.cfs_quota_us 设置为 1,000,000(1 秒)。

    • 对于“E”,我们将 cpu.cfs_period_us 设置为 2,000,000(2 秒),并将 cpu.cfs_quota_us 设置为 2,000,000(2 秒)。

    • 在这种情况下,“D”将获得三倍于“E”的 CPU 时间,但受到 1 秒 CPU 时间配额的限制。一旦“D”达到了 1 秒的配额,它在当前周期的剩余时间内将被暂停。

对应到ubuntu 22.04版本的cpu相关配置

在这里插入图片描述

  1. cpuset.cpus:
    这个文件显示了当前 cgroup 可以使用的 CPU 列表。如果这个文件是空的,表示当前 cgroup 可以使用所有 CPU 资源。

  2. cpuset.cpus.effective:
    这个文件显示了当前 cgroup 实际可以使用的 CPU 列表。这个列表可能会比 cpuset.cpus 更加精确,因为它考虑了父 cgroup 的 CPU 分配情况。

在 cgroup 系统中,每个 cgroup 都可以设置自己的 CPU 分配策略。当一个子 cgroup 被创建时,它会继承父 cgroup 的 CPU 分配策略。
cpuset.cpus 文件表示当前 cgroup 可以使用的 CPU 列表。但是,这个列表可能并不完全准确,因为它没有考虑父 cgroup 的 CPU 分配。
而 cpuset.cpus.effective 文件则考虑了父 cgroup 的 CPU 分配情况,因此它显示的 CPU 列表更加精确。它实际上是根据当前 cgroup 和它的父 cgroup 的 CPU 分配策略计算出来的。
例如,如果父 cgroup 只允许使用 CPU 0 和 CPU 1,那么即使子 cgroup 可以使用所有 CPU,但 cpuset.cpus.effective 也只会显示 CPU 0 和 CPU 1。

  • cpuset.cpus 是空的,表示当前 cgroup 可以使用所有 CPU 资源。
  • cpuset.cpus.effective 显示 0-1,表示当前 cgroup 实际可以使用 CPU 0 和 CPU 1。

这意味着,即使当前 cgroup 可以使用所有 CPU 资源,但由于父 cgroup 的限制,它实际只能使用 CPU 0 和 CPU 1。

通过查看这两个文件,可以了解当前 cgroup 的 CPU 使用情况,并根据需要进行调整。如果需要更改 CPU 分配,可以修改这些文件的内容。

  1. cpu.max
    会根据前一个和后一个的比值来决定cpu的使用率,max代表最大,就是百分之百
  2. memory.max
    max代表最大

top

使用 top 命令可以查看进程使用的 CPU 情况。具体步骤如下:

  1. 运行 top 命令:
top
  1. 按下 1 键切换到 CPU 信息视图。这时你会看到每个 CPU 核心的使用情况。

  2. 要查看进程使用的 CPU 信息,可以按下 c 键切换到显示命令行模式。这样可以看到每个进程使用的 CPU 核心。

在这里插入图片描述

注意

如果父 cgroup 的 cpuset.cpus.effective 设置小于子 cgroup 的 cpuset.cpus.effective 设置,那么子 cgroup 中的进程实际上只能使用父 cgroup 允许的 CPU 范围。

具体来说:

  1. 当父 cgroup 的 cpuset.cpus.effective 被设置为某个范围(比如 0-1)时,所有子 cgroup 都会继承这个 CPU 范围。

  2. 即使子 cgroup 将 cpuset.cpus 设置为一个更大的范围(比如 0-3),但实际可用的 CPU 范围仍然被限制在父 cgroup 的 cpuset.cpus.effective 中(即 0-1)。

  3. 子 cgroup 进程只能使用父 cgroup 允许的 CPU 范围,即使子 cgroup 自己的设置允许使用更多 CPU。

  4. 如果父 cgroup 的 cpuset.cpus.effective 被修改,子 cgroup 的可用 CPU 范围也会相应地更新。

并且最开始的/sys/fs/cgroup没有 cpuset.cpus,只有 cpuset.cpus.effective ,但把当前可用的都包括到cpuset.cpus.effective里面去了,后面新建的cgroup中就有cpuset.cpuscpuset.cpus.effective 了,而且在父cgroup cpuset.cpus.effective 的允许范围内和cpuset.cpu的双重限制后cpuset.cpus.effective会自动变化(当cpuset.cpus`变化后)

查看你可使用的cpu

要查看当前可使用的 CPU 序号,您可以使用以下方法:

  1. 使用 lscpu 命令:
lscpu

这个命令会显示系统中 CPU 的详细信息,包括 CPU 数量和 CPU 序号。

  1. 使用 cat /proc/cpuinfo 命令:
cat /proc/cpuinfo

这个命令会输出系统中所有 CPU 的详细信息,包括 CPU 序号。
在这里插入图片描述

注意

发现运行一次后,第二次运行失败,原因是挂载的文件系统/proc没有卸载掉,卸载或者重新挂载到当前/proc即可

context.Args():所有flag识别后剩余的就是Args()的
context.Bool():只要有-it就行,不会识别后面的一个参数
context.String():会识别后面的一个参数,将其作为标签对应的字符串

启动后的top查看

由于启动后将/proc文件系统挂载到运行的地方去了,本地使用top时发现不行,此时再挂载回来即可
在这里插入图片描述

显示进程使用的cpu序号

要查看当前进程使用哪个 CPU,可以在 top 命令中使用一些额外的选项。

步骤如下:

  1. 首先确保 /proc 文件系统已经正确挂载,如果没有挂载可以使用以下命令进行挂载:

    sudo mount -t proc proc /proc
    
  2. 运行 top 命令,并按下 1 键查看每个 CPU 的使用情况。

  3. 要查看进程使用的具体 CPU 核心,可以按下 f 键进入配置模式,然后选择 %CPUP 两个字段。

    • %CPU 显示进程使用的 CPU 百分比
    • P 显示进程使用的 CPU 编号
  4. 按下 d 键可以设置刷新频率,然后按下 Enter 键确认。

  5. 现在 top 界面上就会显示每个进程使用的具体 CPU 编号了。

代码

https://github.com/FULLK/llkdocker/tree/main/cgroup_docker

结果

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Vol.45 这个壁纸网址,功能简单,每月37.7万访问量

哈咯,大家好,我是欧维,今天要给大家分享的网站是:极简壁纸,一个专门做电脑壁纸的网站; 它的网址是:极简壁纸_海量电脑桌面壁纸美图_4K超高清_最潮壁纸网站 网站的壁纸质量很高,页面…

Java 模块化开发

前言 之前在 Github 下载的好多代码发现都是 Java 模块化开发出来的,模块化是 JDK9 引入的,所以在 JDK9 及其后续的版本中,都可以采用模块化开发的方法来进行项目的开发。尤其是Java桌面应用开发,虽然这只是我的一个业余爱好&…

WindowsServer 2022 AD域控-006-安装副域控

试验拓扑图: 一、测试单域控故障,用户无法修改密码; 域控断网,Win10测试; 二、WindowsServer2022 DC02加入域控; 加入成功 此时域控上只有DC02这台服务器,但DC02并不是域控; 三、WindowsS…

防汛物资仓库管理系统|实现应急物资仓库三维可视化

系统概述 智慧应急物资仓库可视化系统(智物资DW-S300)采用了 B/S 架构的设计,通过浏览器即可快速登录操作。实现对库房内的应急物资从申购入库、出库、调拨、库内环境监测、维修保养、检测试验、处置报废等全周期、科学、规范的管理。系统以…

使用DockerCompose安装Redis

本文使用docker-compose的方式安装Redis,如何未安装docker-compose,可以参考这篇文章进行安装【在Ubuntu上安装Docker Compose】 一、创建一个DockerCompose配置文件 第一步:创建相关目录文件 为了更好的组织管理Docker容器的配置文件和映射…

python linux服务器ssh简单爆破(测试用户名密码)(连接ssh服务器)(测试登录ssh服务器)

文章目录 背景示例代码代码解释导入模块SSH服务器的地址和端口用户名和密码列表生成所有可能的用户名和密码组合尝试连接到SSH服务器并验证用户名和密码遍历并测试每一对凭证 背景 我们华为摄像头linux终端的密码忘了,还不太好初始化,手动一个个测试太麻…

每日一题——阶乘计算升级版

题目链接: 6-10 阶乘计算升级版 - 基础编程题目集 (pintia.cn) 题目: 6-10 阶乘计算升级版 分数 20 本题要求实现一个打印非负整数阶乘的函数。 函数接口定义: void Print_Factorial ( const int N ); 其中N是用户传入的参数&#xff…

Nacos的安装(windows环境下)

1. 下载Nacos安装包 点击下载安装包 将安装包解压到本地目录下(路径中不要带中文) 修改配置文件 修改为本地M有SQL的用户名和密码: 启动Nacos 1)打开nacos文件所在的bin目录下 2)用cmd命令行窗口打开在bin目录下 …

Kubernetes的Ingress Controller

前言 Kubernetes暴露服务的方式有一下几种:LoadBlancer Service、ExternalName、NodePort Service、Ingress,使用四层负载均衡调度器Service时,当客户端访问kubernetes集群内部的应用时,数据包的走向如下面流程所示:C…

JS/TS笔记学习1

周末总得学点什么吧~ 奥利给! 跑火车 递归 减速 let currentIndex 0; let speed 500; // 初始速度,单位是毫秒 let decrement 20; // 每次迭代速度减少的量 const cells document.querySelectorAll(.cell); function highlightCell() { cells.forEach(…

vue3 uniapp微信登录

根据最新的微信小程序官方的规定,uniapp中的uni.getUserInfo方法不再返回用户头像和昵称、以及手机号 首先,需获取appID,appSecret,如下图 先调用uni.getUserInfo方法获取code,然后调用后台的api,传入code&…

AI禁区徘徊监测识别摄像机

AI禁区徘徊监测识别摄像机是一种基于人工智能技术的智能监控设备,用于监测禁止进入或逗留的区域。这种摄像机通过高清摄像头实时捕捉场景图像,利用AI算法对人员徘徊行为进行识别和监测,有助于提高安全防范水平,减少潜在的安全风险…

免费分享Springboot+Vue的影院管理系统源码,真酷!

今天给大家分享一套基于SpringbootVue的影院管理系统源码,在实际项目中可以直接复用。(免费提供,文末自取) 一、系统运行图 1、登陆页面 2、系统后台 3、选座功能 影院管理系统通常具有以下七个功能点: 1.电影管理: 包括电影信…

CMC学习系列 (8):动态力输出期间的伽马范围皮质相干性

CMC学习系列:动态力输出期间的伽马范围皮质相干性 0. 引言1. 主要贡献2. 方法3. 结果4. 讨论5. 总结欢迎来稿 论文地址:https://www.sciencedirect.com/science/article/abs/pii/S1053811906010238 论文题目:Gamma-range corticomuscular coherence duri…

ARM v8 Cortex R52内核 04 时钟和复位 Clocking and Resets

ARM v8 Cortex R52内核 04 时钟和复位 Clocking and Resets 4.1 Clock and clock enables 时钟和时钟使能 Cortex-R52处理器具有一个单一的时钟,驱动着所有的触发器和RAM。各种输入,包括复位输入,都有同步逻辑使它们可以与处理器时钟异步操…

C语言 | Leetcode C语言题解之第20题有效的括号

题目&#xff1a; 题解&#xff1a; char pairs(char a) {if (a }) return {;if (a ]) return [;if (a )) return (;return 0; }bool isValid(char* s) {int n strlen(s);if (n % 2 1) {return false;}int stk[n 1], top 0;for (int i 0; i < n; i) {char ch pair…

模型训练-保存训练数据

1.目的 找到一个可运行的代码&#xff0c;可以每个epoch打印训练数据&#xff0c;但是不会保存。因为在改进模型需要这些训练数据进行对比&#xff0c;所以需要将每个epoch的训练数据保存下来&#xff0c;写到一个文件中。 2.解决方案1 直接问ChatGPT&#xff0c;提示词如下…

5.4Python之可变类型与列表的深浅拷贝

【1】可变类型与不可变类型 在Python中&#xff0c;数据类型可以分为可变类型&#xff08;Mutable&#xff09;和不可变类型&#xff08;Immutable&#xff09;。这指的是对象在创建后是否可以更改其值或状态。 不可变类型是指创建后不能更改其值或状态的对象。如果对不可变类…

二极管分类及用途

二极管分类及用途 通用开关二极管 特点&#xff1a;电流小&#xff0c;工作频率高 选型依据&#xff1a;正向电流、正向压降、功耗&#xff0c;反向最大电压&#xff0c;反向恢复时间&#xff0c;封装等 类型&#xff1a;BAS316 ; IN4148WS 应用电路: 说明&#xff1a;应用…

单路高清HDMI编码器JR-3211HD

产品简介&#xff1a; JR-3211HD单路高清HDMI编码器是专业的高清音视频编码产品&#xff0c;该产品具有支持1路高清HDMI音视频采集功能&#xff0c; 1路3.5MM独立外接音频输入&#xff0c;编码输出双码流H.264格式&#xff0c;音频MP3/AAC格式。编码码率可调&#xff0c;画面质…