开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

git 获取代码贡献量,并用echarts展示 在要统计的项

发表于 2024-04-24

在要统计的项目目录中执行命令

windows用户需要在git bash 中执行命令

带时间范围

1
bash复制代码git log --since="2024-04-23 00:00:00" --until=2099-05-20 --format='%aN' | sort -u | while read name; do echo -en "\n{name: '$name',"; git log --since="2024-04-23 00:04:00" --until=2099-05-20 --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 + $2 } END { printf "addedLines: %s, removedLines: %s, totalLines: %s},", add, subs, loc }' -; done
  • 可以设置时间范围排除项目初始化initial 提交。
  • 这段命令中since是指定开始时间,这段命令中使用了两个since,第一个是作用于前面的git log提交者的name,第二个是作用于后面的git log。

查看全部的

1
bash复制代码 git log --format='%aN' | sort -u | while read name; do echo -en "\n{name: '$name',"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 + $2 } END { printf "addedLines: %s, removedLines: %s, totalLines: %s},", add, subs, loc }' -; done

命令效果

image.png

echarts 配置

替换下面list中的内容,把代码替换页面中的js
echarts在线地址: Examples - Apache ECharts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
js复制代码let list = [
{
name: 'Caichong',
addedLines: 3,
removedLines: 15,
totalLines: 18
},
{
name: 'Caisin',
addedLines: 1,
removedLines: 1,
totalLines: 2
},
{
name: 'Cao Duc Anh',
addedLines: 58,
removedLines: 31,
totalLines: 89
},
{
name: 'Captain',
addedLines: 113,
removedLines: 8,
totalLines: 121
},
{
name: 'Carfield',
addedLines: 2,
removedLines: 2,
totalLines: 4
},
{
name: 'Carlos',
addedLines: 1,
removedLines: 1,
totalLines: 2
},
{
name: 'Carson',
addedLines: 120,
removedLines: 135,
totalLines: 255
}
];

list = list.sort((a, b) => a.totalLines - b.totalLines)

const colors = {
addedLines: '#228B22',
removedLines: '#DC143C',
totalLines: '#FFD700'
};

const series = ['addedLines', 'removedLines', 'totalLines'].map((key) => ({
name: key,
type: 'bar',
barGap: '0%',
itemStyle: {
color: colors[key]
},
label: {
show: true,
position: 'top'
},
barCategoryGap: '50%',
data: list.map((v) => v[key])
}));

option = {
legend: {
show: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
axisLabel: {
show: true,
interval: 0
},

data: list.map((v) => v.name)
},
series,
yAxis: {
type: 'value'
}
};

echarts 效果

image.png

结束

我尝试用nodejs实现,导出一个json,然后输出一个页面,用echarts展示,发现在window下用node执行上面的命令会报错,有兴趣的可以自己实现一下写个插件发npm出来。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

推荐 7 个 vscode 插件,让 coding 更加丝滑

发表于 2024-04-24

Project Manager

Project Manager 是一个项目管理插件,可以帮助你更轻松的管理和组织项目。如果你有过同时开发多个项目的经历,一定会体会过在不同项目之间切换的苦恼。每个项目开一个 vscode 窗口的话,电脑可能会变的卡顿,只开一个窗口的话,切换起来由比较麻烦,只能从 File -> Open Recent 菜单选择。有了这个插件,你就可以把项目都加到 list 中,如果你想切换项目,直接按下 option + command + p即可唤起项目列表,然后输入要切换的项目,回车就好了

Echarts Enhanced Completion

这是一个用于编辑 echarts 的配置项时进行补全提示的 vscode 的扩展。安装并启用插件后,在你需要用作 echarts 配置项的对象的上一行添加一行注释: /** @type EChartsOption */ (输入 echartsoption 可以使用代码片段),用于定义该对象为配置项对象。现在,在配置项对象中按下 Enter 键时,插件会显示你可能需要的配置项的列表,并且提示中有对该配置项的详细说明。

draw.io

Draw.io Integration 是一个在 Visual Studio Code 中集成 Draw.io 的插件,它允许你在 VSCode 中直接编辑和预览 Draw.io 图表。以下是该插件的一些主要功能和特点

  1. 在 VSCode 中编辑图表: 你可以在 VSCode 中直接编辑 Draw.io 图表,无需离开编辑器即可进行图表的创建、编辑和调整。
  2. 实时预览: 插件提供了实时预览功能,可以在编辑 Draw.io 图表时立即预览图表的效果,使得调整和修改更加直观。
  3. 支持多种图表类型: 插件支持 Draw.io 中的多种图表类型,包括流程图、组织结构图、UML 图、网络图等,满足不同场景下的需求。
  4. 保存和导出: 你可以将编辑完成的图表保存到本地或者导出为图片或者 XML 文件,方便分享和使用。
  5. 与其他插件集成: 插件与其他常用的 VSCode 插件集成良好,例如 Git 插件、Markdown 插件等,可以更加方便地在项目中使用 Draw.io 图表。

Code Runner

Code Runner 是一个在 Visual Studio Code 中执行代码的插件,它支持多种编程语言,并且提供了一键执行代码的功能。以下是该插件的一些主要特点和功能:

  1. 支持多种编程语言: Code Runner 支持多种常见的编程语言,包括但不限于 JavaScript、Python、Java、C++、C#、Go、PHP、Ruby、Swift、TypeScript 等。
  2. 一键执行代码: 你可以通过点击编辑器右上角的运行按钮或者使用快捷键来一键执行当前编辑的代码,无需额外的配置或者切换环境。
  3. 支持代码片段: 你可以选择部分代码进行执行,而不是执行整个文件,这在调试代码或者测试特定功能时非常方便。
  4. 自定义配置: 插件提供了丰富的配置选项,你可以根据需要自定义执行命令、参数、语言解释器等,以满足不同编程环境下的需求。
  5. 输出结果显示: 在代码执行完成后,插件会将执行结果显示在输出面板中,包括标准输出、标准错误、执行时间等信息,方便查看和调试。

Hex to rgb

这是一款用于转换 hex 和 rgb 的插件,在前端开发中。经常会碰到需要把一个 hex 颜色改成带有透明度的 rgba 格式,在没有这款插件之前,我一般会把 hex 复制到在线的转换网站中,转换成 rgb 后再复制到代码中,最后再修改透明度。有了这款插件以后,我们就可以直接一键实现 hex 和 rgb 的互转,我这里结合了 vim 来实现,绑定了相关的快捷键为 rgb -> hex leader + h ,hex -> rgb leader + r

Material Icon Theme

这是一款 Meterial 风格的文件 Icon 美化插件,让你的资源管理器更加的美观,心情好了,coding 效率才能上去。

Turbo Console Log

Turbo Console Log 插件通过自动化写入有意义的日志消息的操作,让你在调试时输出变量更加简单,不用再输入 console.log(),而是可以直接通过快捷键 option + shift + L快速打印变量

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JVM 应用被 K8S 莫名 Kill 的生产事故排查(基于

发表于 2024-04-24

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

本篇文章不涉及底层的 JDK bug 之类的问题,更主要的是JVM 内存分析思路和 NMT 相关的一些用法。

♟️先来一个场景

我们生产中出现了一个情况,当我们的核心应用连续运行超过半个月的时候,就会莫名的宕掉。

由于我们是 K8S 集群,所以是被 K8S 发送 kill 命令杀掉的,K8S 杀掉应用的原因是 OOM。

这个 OOM 不是我们 Java 中常说的那个 OOM 异常,而是 K8S 日志中给出的杀掉这个实例的原因,这个 OOM 的意思是:实例使用内存超过了 K8S 设置的实例内存大小限制,所以 K8S 将这个实例给杀掉了。

大家可以先思考一下这个场景,然后想一下自己的排查思路~

我同事判断这是内存泄露,但是正常 JVM 内存泄露它死亡前会生成一个 dump 文件,我们一开始没有拿到这个文件,因为在没有完全生成这个文件的时候 K8S 就把实例杀了(Dump 是单线程的,所以会耗时过长)。

后来我们终于拿到这个文件之后,开始了排查,然后还真的找到了几处内存泄露的地方做了优化。

但是事情远没有结束,做完优化只是延长了这个实例存活的时间,过一段时间以后依然会被 K8S 以 OOM 的原因 Kill 掉。

后来我同事由于太忙(一直没能彻底解决🤣),就委托我来开始排查,这才开始了我的排查之路~

😨内存一切正常?

开始接手排查后,我做的第一件事是问运维要了 gclog, 然后通过 GcEasy 进行在线日志分析,GcEasy 的使用比较简单,只需要将自己的 gclog 文件上传给它:

Image.png

然后点击分析就可以即时生成一个 gclog 图表,类似下面这样的:

Image.png

Image.png

如果有一些比较明显的问题,GcEasy 的报告中还会直接提醒你出现了 memory problem。

在我的这个场景中,我只需要关注下面几个指标就可以了:

  1. gc 平均耗时与最大耗时。
  2. gc 的内存回收曲线图。

GC 的平均耗时与最大耗时是来评估应用是否会发生明显卡顿,如果 gc 有长耗时,则应用一定会卡顿。

如果 gc 的某一次 FullGC 耗时过长则可能会影响 K8S 对此应用是否健康的判断,所以这点是我需要关注的。

第二个就是 gc 内存回收曲线, 因为应用往往是运行一段时间后才死掉,所以推测是不是由于内存泄漏导致大量对象得不到回收,一直被留在 Old 区域中,最终在 FullGC 也无法有效回收 Old 区域的情况下,一波业务流量高峰导致了JVM OOM, 然后在 OOM Dump的过程中导致宿主机也内存不足,最终触发 K8S 的OOM 导致被 Kill 掉。

虽然第二种推测的可能性更大一些,但是也有可能JVM 应用在还没触发宿主机的 OOM 就被宿主机的内核杀掉了。

所以不出乎我意料的是,这两个问题都没有出现,一是 gc 耗时并不长, young 区大概 50-150ms,old 区也不超过 1s。

二是在应用被干掉的前一刻,young 区和 old 区的内存都还有一半的空余(因为每次回收都能回收大量对象,Old 区也没有内存泄漏)~

排除了这两个可能后,内存问题很明显在直接内存区域(或者说非堆内存区域)。

所以我开始通过 GcEasy 的报告来查看 MetaSpace 当前的申请值,遗憾的是,它也远没有达到阈值。

当时问题排查到这里,排查思路就要换一下了,我的大脑中开始疯狂搜索有没有在代码中使用 JDK 的 NIO,或者是其他一些申请堆外内存的地方。

但是作为业务开发来说,我们一般是不会直接使用堆外内存的,使用 NIO 的只有一些常用的 Web 框架,所以我暂时排除了代码问题,打算直接开始分析堆外内存的变化。

🤔Native Memory Tracking

思索了一番后,我决定使用 Native Memory Tracking (下文简称 NMT)进行堆外内存的排查,这是一个 JVM 官方的监控与分析工具,它被直接集成在 JVM 中,虽然它并不能统计到 JDK API 申请内存的操作(比如:DirectBuffer),但是在这一步,我还是打算先使用它观察,因为它方便。

由于使用它对应用性能具有一定影响,所以默认是关闭状态,需要通过 JVM 参数打开。

我们可以通过以下两个参数进行打开:

1
2
xml复制代码-XX:NativeMemoryTracking=summary
-XX:NativeMemoryTracking=detail

一般使用第一个即可,第二个具有较大的性能影响,也并不适合我们排查问题。

注:此参数不能通过 jcmd 进行动态打开,需要在启动应用时带上此参数。

由于我们使用它时只能打印当前的应用状态,所以我将通过一个定时脚本的方式持续打印应用的内存情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码@Scheduled(cron = "0 0/5 * * * ?")
public void nativeMemoryPrint() {

String host = "";

try {
host = InetAddress.getLocalHost().getHostAddress();

String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];

String rollup = Files.readAllLines(Paths.get("/proc/" + pid + "/smaps_rollup"))
.stream()
.collect(Collectors.joining("\n"));

log.info("{} 容器实例内存监控 pod : {}", host, rollup);

Process exec = Runtime.getRuntime().exec(new String[]{"jcmd", pid, "VM.native_memory"});

String nativeMemoryLog = new BufferedReader(new InputStreamReader(exec.getInputStream())).lines().collect(Collectors.joining("\n"));

log.info("{} 容器实例内存监控 JVM 进程 : {}", host, nativeMemoryLog);

} catch (IOException e) {
log.error("{} 容器实例内存监控出错 : {}", host, e.getLocalizedMessage());
e.printStackTrace();
}
}

注:这个脚本原型来自张哈希张哥,我这里做了一些修改就直接使用了~

通过上面的例子可以看到我使用了两个命令,jcmd 用来打印 JVM 应用的 NMT 信息,使用 smaps_rollup 观测了具体占用。

之所以要加上 smaps_rollu是因为 NMT 打印的结果由于 JVM 内存分配机制的原因不够完全准确,这时候要看实际占用的内存。

通过这两个数据对比着看,来观测是不是进程内存超出了分配内存大小。

先来看看smaps_rollup 的打印:

1
2
3
4
5
java复制代码> cat /proc/23/smaps_rollup 
689000000-fffff53a9000 ---p 00000000 00:00 0 [rollup]
Rss: 5870852 kB
Pss: 5849120 kB
Pss_Anon: 5842756 kB

这里我们着重来看 Pss 的值即可,可以把它理解为进程实际占用内存大小。

再来看看 NMT 的打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码Total: reserved=10575644KB, committed=443024KB
- Java Heap (reserved=8323072KB, committed=192512KB)
(mmap: reserved=8323072KB, committed=192512KB)

- Class (reserved=1050202KB, committed=10522KB)
(classes #15409)
( instance classes #14405, array classes #1004)
(malloc=1626KB #33495)
(mmap: reserved=1048576KB, committed=8896KB)
( Metadata: )
( reserved=57344KB, committed=57216KB)
( used=56968KB)
( waste=248KB =0.43%)
( Class space:)
( reserved=1048576KB, committed=8896KB)
( used=8651KB)
( waste=245KB =2.75%)

- Thread (reserved=669351KB, committed=41775KB)

由于 NMT 的打印太长,这里我只截取了一部分,每个指标上面都有一个 reserved 和 committed,reserved 代表了计划申请的内存,committed 代表了实际申请的物理内存,加之 JVM 具有动态回收的特性,它回收的内存可能不会立马还给操作系统,这里会有一点出入。

如果 committed 和 上面的 Pss 都显示如果所要用的内存已经将要超出宿主机内存,那就代表容器宿主机有被 K8S 杀掉的风险。

😤破案

把上面的脚本发布上线之后, 我就开始了静静等待,就像罗辑在等着太阳系外的咒语生效一样。

终于有一天周一上班运维给我说昨晚上容器又被 kill 了,我赶紧去看相关日志,由于我的日志是 5s 一次,所以能清晰的看到容器在死亡之前的内存状态。

最终证明了我的猜想,容器频繁死亡是因为内存不足,但是不是堆内存而是非堆,最终在我给容器加了 2G 内存之后这个问题再也没有出现过。

PS:其实没排查前我就想加,运维不让,他说你得给我一个理由。。。

等等,一般到这里故事还没有结束,接下来我应该分析到底哪个部分出了问题,是元空间?直接内存?线程内存?

但是我没有再分析下去了,因为复现这个问题要等两周的时间,而且通过我现在了解的信息还不足以证明到底是哪里出现了问题。

通过上面的分析,我们现在得知了:堆 + 元空间 + 其他内存区域的总内存占大小,但是我们还有一些是不知道的,就是我在文章开头提到过的:DirectBuffer 和 MappedByteBuffer,但是他们可以被一个 JVM 参数限制:-XX:MaxDirectMemorySize。

所以把这上面的加一块,就是所有的内存占用总大小了吗?仍然不是,这个参数无法限制 Unsafe 分配的内存。

如果想要彻底的排查出来,可能需要花费更多的时间,而现在问题已经解决了。

以问题为导向,解决了问题,就解决了一切。

毕竟 99% 的区域都是可以被 JVM 参数控制的,剩下的,我当时也不想深究了,毕竟上班很忙,程序员的时间可是很宝贵的~


感谢大家能看到这,同时也希望大家能对本篇点点赞,点赞过 100 一周内更新更多高级 Java 知识,有任何问题都可以在评论区一块讨论,祝有好收获。

注:文中的两个打印结果是引用的张哈希张哥的,因为自己懒得跑jvm 了😭,也推荐一下张哈希关于 NMT 的文章,干货满满~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

制作电子贺卡,用最高端的方式表达最美好的祝福 前言

发表于 2024-04-24

前言

亲爱的掘友,假设若干年之后,你的好朋友结婚了,而作为一个对编码非常有兴趣的我们,除了手写贺卡表达祝福之外,是否可以运用我们的知识所学,去制作一个电子的贺卡,以表达最美好的祝福呢?

小项目准备工作

  1. html,css,js相关的基本知识
  2. 贺卡的祝福语,贺卡的封面和里面的图片

html部分

重点部分说明

这段代码是一个简单的HTML结构,其中包含了一个书籍的封面和祝福文字。

  1. HTML(Hypertext Markup Language) : HTML是用于创建网页的标记语言。它由一系列标签组成,这些标签描述了文档的结构和语义。
  2. CSS(Cascading Style Sheets) : CSS用于控制网页的样式和布局。在这段代码中,应用了一些CSS类来定义书籍封面的样式,例如.book-cover和.front-cover。
  3. 3D 变换(3D Transformations) : 代码中使用了一些带有3D效果的CSS类,如.p3d和.flip,这些类可以通过CSS 3D变换属性(例如transform)来实现元素在3D空间中的旋转、倾斜等效果。
  4. DIV(Division)元素: <div>元素是HTML中用于创建容器的标签,可以将其他元素组织在一起,并对它们应用样式或脚本。
  5. 文本内容: 在代码中包含了一段祝福小王和小张的结婚祝福语录,通过<p>标签表示段落,这段文字将显示在书籍封面的正面。

这些基础知识涵盖了HTML和CSS的基本概念,以及一些关于3D变换的简单理解。

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
xml复制代码  <div class="book p3d"> <!-- 书籍容器 -->

<!-- 书籍封面 -->
<div class="book-cover p3d">
<!-- 背面页 -->
<div class="page back flip"></div>
<!-- 正面页 -->
<div class="page front p3d">
<!-- 阴影效果 -->
<div class="shadow"></div>
<!-- 图片(可能是封面图) -->
<div class="pic"></div>
</div>
</div>

<!-- 书籍正面封面 -->
<div class="front-cover p3d">
<!-- 正面页 -->
<div class="page front flip p3d">
<!-- 祝福文字 -->
<p>
祝福小王和小张,从此携手共渡人生的每一刻,相互扶持,相互
理解,共同构筑一座幸福的家园。婚姻是生活的新起点,愿
你们在这段新的旅程中,愿你们的婚姻像一朵绽放的鲜花,散发
着芬芳和美好;像一颗稳固的灯塔,照亮前行的路途。无论是
欢乐还是挑战,都能紧握彼此的手,共同面对,共同成长。愿你们的
爱情像阳光一样温暖,像清风一样
舒爽,永远绽放着美丽和幸福。愿你们的婚姻像一首美妙的乐章
,奏响幸福的旋律,永远充满着欢笑和快乐。祝福你们,永远幸福美满!
</p>
</div>
<!-- 背面页 -->
<div class="page back"></div>
</div>

</div>

html部分小结

我们描述了一个带有3D效果的书籍容器,其中包括书籍封面和书籍正面封面。

  1. <div class="book p3d">:这是整个书籍容器的主要部分,具有类名”p3d”,表示使用了某种3D效果。
  2. <div class="book-cover p3d">:这是书籍封面的部分,同样具有类名”p3d”,表示封面也应用了3D效果。
* `<div class="page back flip"></div>`:这个元素代表书籍封面的背面页,有翻转的效果,类名"flip"指示它可以实现翻转动画效果。
* `<div class="page front p3d">`:这个元素代表书籍封面的正面页,同样具有类名"p3d",表示它也应用了3D效果。


    + `<div class="shadow"></div>`:这个元素代表书籍封面的阴影效果。
    + `<div class="pic"></div>`:这个元素代表书籍封面的图片,即封面图。
  1. <div class="front-cover p3d">:这是书籍正面封面的部分,同样具有类名”p3d”,表示正面封面也应用了3D效果。
* `<div class="page front flip p3d">`:这个元素代表正面封面的正面页,具有翻转和3D效果。


    + `<p>...</p>`:这个段落包含了祝福的文字内容,描述了对小王和小张的祝福。
* `<div class="page back"></div>`:这个元素代表正面封面的背面页。

css部分

接下来,让我们用css对它进行装饰

重点部分说明

要理解这段CSS代码,首先需要一些基础知识:

  1. Box Model:了解盒模型,包括内容、内边距、边框和外边距等部分,以及如何使用box-sizing属性来调整盒模型的计算方式。
  2. CSS选择器:熟悉CSS选择器,包括元素选择器、类选择器、ID选择器、伪类和伪元素等,以及它们的优先级规则。
  3. Transform和Perspective:了解CSS的transform属性和perspective属性,以及它们在页面中创建2D和3D变换效果的能力。
  4. CSS动画和过渡:了解如何使用CSS动画和过渡来实现元素的动态效果。
  5. 背景图像和渐变:了解如何使用CSS设置背景图像和渐变色。
  6. 视角和透视:理解透视视角的概念,以及如何使用透视属性来创建3D效果。

一旦掌握了这些基础知识,就可以开始解析这段代码。我们主要实现了一个带有3D效果的书籍容器,其中包括书籍封面和正面封面。具体的CSS规则会应用于不同的元素,例如.book、.page、.front、.back等,以及一些属性如transform、background-color、background-image等,用来定义元素的外观和行为。

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
css复制代码/* 重置默认样式 */
* {
margin: 0;
padding: 0;
border: 0;
box-sizing: border-box;
}

/* 设置HTML元素高度为100% */
html {
height: 100%;
}

/* 设置body元素高度为100%,定义字体、颜色和透视效果 */
body {
height: 100%;
font: 100%/1.25 Helvetica, arial, helvetica; /* 字体设置为Helvetica */
color: #fff; /* 文字颜色为白色 */
perspective: 1000px; /* 设置透视效果的视距为1000px */
background: linear-gradient(to bottom, #444, #999); /* 设置背景为线性渐变 */
}

/* 书籍容器样式 */
.book {
width: 300px;
height: 300px;
position: absolute;
left: 50%; /* 位于父元素水平居中 */
margin-left: -150px; /* 负的一半宽度,实现水平居中 */
top: 50%; /* 位于父元素垂直居中 */
margin-top: -150px; /* 负的一半高度,实现垂直居中 */
cursor: pointer; /* 鼠标移上去显示指针 */
user-select: none; /* 禁止用户选择内容 */
transform: rotateX(60deg); /* 沿X轴旋转60度,实现倾斜效果 */
}

/* 页面样式 */
.page {
width: 300px;
height: 300px;
padding: 1em; /* 设置内边距 */
position: absolute;
left: 0;
top: 0;
text-indent: 2em; /* 文本首行缩进2em */
}

/* 正面样式 */
.front {
background-color: #d93e2b; /* 设置背景颜色 */
}

/* 背面样式 */
.back {
background-color: #fff; /* 设置背景颜色为白色 */
}

/* 封面正面样式 */
.front-cover {
transform-origin: 0 50%; /* 设置旋转中心为左边缘的中点 */
transform: rotateY(0deg); /* 沿Y轴旋转0度,初始状态 */
}

/* 3D效果样式 */
.p3d {
transform-style: preserve-3d; /* 设置子元素在三维空间内保持平铺 */
}

/* 封面背面样式 */
.front-cover .back {
background-image: url(./微信截图_20240421113536.png); /* 设置背景图像 */
background-size: 100%; /* 图像大小为100% */
transform: translateZ(3px); /* 沿Z轴平移3px,实现3D效果 */
}

/* 翻转效果样式 */
.flip {
transform: rotateY(180deg); /* 沿Y轴旋转180度,实现翻转效果 */
}

/* 阴影和图片容器样式 */
.shadow,
.pic {
width: 196px;
height: 132px;
position: absolute;
left: 60px;
top: 60px;
transform-origin: 0 100%; /* 设置旋转中心为左下角 */
}

/* 图片容器样式 */
.pic {
background: url(./微信截图_20240421113550.png); /* 设置背景图片 */
background-size: cover; /* 图片大小为覆盖容器 */
}

/* 阴影样式 */
.shadow {
background-color: rgba(0, 0, 0, 0.5); /* 设置半透明黑色背景 */
}

css部分小结

这段 CSS 代码主要用于创建一个具有翻书效果的书籍样式。

  1. 通用样式:
* 将所有元素的边距、填充、边框设置为零,盒子模型设为边框盒模型。
  1. HTML 和 Body 样式:
* 设置 HTML 和 Body 元素的高度为100%。
* 设置页面字体为 Helvetica,字体大小为100%的基础上增加25%。
  1. 书籍容器样式 (.book):
* 设置书籍容器的宽度和高度为300px,绝对定位并水平垂直居中。
* 鼠标悬停时显示指针,并禁止选择内容。
* 应用3D旋转效果,使其看起来像倾斜的书本。
  1. 页面样式 (.page):
* 设置页面的宽度和高度为300px,绝对定位在左上角。
* 设置内边距为1em,并设置文本首行缩进为2em。
  1. 正面样式 (.front) 和 背面样式 (.back):
* 设置正面背景颜色为 #d93e2b(深红色),背面背景颜色为白色。
  1. 封面样式 (.front-cover):
* 设置封面旋转的原点在左侧中点。
* 设置封面的背面为一个指定的图片,并应用3D效果。
  1. 3D效果样式 (.p3d):
* 设置子元素在三维空间内保持平铺。
  1. 翻转效果样式 (.flip):
* 通过旋转180度实现翻转效果。
  1. 图片容器样式 (.pic) 和 阴影样式 (.shadow):
* 设置图片容器和阴影的样式和位置,其中图片容器有一张指定的背景图片,阴影为半透明黑色。

js部分

重点部分说明

这段 JavaScript 代码实现了鼠标按住并移动时的交互效果,主要用于控制书籍封面的翻转和阴影效果。

  1. 定义了变量 hold,用于标识鼠标是否按住。
  2. 获取了书籍封面的元素节点,并定义了一个函数 clamp 用于限制值的范围在指定的最小值和最大值之间。
  3. 给封面元素添加了鼠标按下事件监听器 (onmousedown),当鼠标按下时将 hold 设置为 true。
  4. 给窗口添加了鼠标松开事件监听器 (onmouseup),当鼠标松开时将 hold 设置为 false。
  5. 给窗口添加了鼠标移动事件监听器 (onmousemove),当鼠标移动时执行相应操作:
* 如果鼠标按住 (`hold == true`),则执行以下操作:


    + 计算鼠标移动的角度,并根据窗口宽度和鼠标位置来计算旋转角度,使封面随鼠标移动而旋转,实现翻书效果。
    + 根据计算得到的角度,通过设置封面元素的 `transform` 属性来实现旋转。
    + 针对书籍封面上的图片容器 (`pic`) 和阴影 (`shadow`),可能还需要根据旋转角度来调整它们的倾斜角度,以使得交互效果更加逼真。

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
xml复制代码<script>
// 鼠标摁住事件
// 鼠标移动事件

// 标识鼠标是否按住的变量
var hold = false;

// 获取书籍封面的元素节点
var page = document.querySelector('.front-cover');

// 限制值的范围在指定的最小值和最大值之间的函数
var clamp = function (val, min, max) {
return Math.max(min, Math.min(val, max));
}

// 鼠标按下事件监听器
page.onmousedown = function () {
hold = true; // 当鼠标按下时,将 hold 设置为 true
}

// 鼠标松开事件监听器
window.onmouseup = function () {
hold = false; // 当鼠标松开时,将 hold 设置为 false
}

// 鼠标移动事件监听器
window.onmousemove = function (e) { // 只有在鼠标摁住的情况下才执行
if (hold == true) { // 如果鼠标按住了
console.log(e.pageX); // 输出鼠标当前位置的横坐标

// 计算封面的旋转角度
var angle = clamp((window.innerWidth / 2 - e.pageX + 300) / 300 * -90, -180, 0);

// 设置封面元素的 transform 属性,使其随鼠标移动而旋转
page.style.transform = `rotateY(${angle}deg)`;

// 对书籍封面上的图片容器(pic)进行立起来的旋转,绕 x 轴旋转 angle / 2
// 对阴影(shadow)进行倾斜,沿 x 轴倾斜 angle / 8
}
}
</script>

js部分小结

当鼠标按下时,设置 hold 变量为 true,表示鼠标已按下。 当鼠标松开时,将 hold 变量设置为 false,表示鼠标已经松开。 监听鼠标移动事件,在鼠标移动时执行相应操作。 通过 document.querySelector('.front-cover') 获取页面中 class 为 .front-cover 的元素。 定义了一个 clamp 函数,用于限制一个值在指定范围内。 计算旋转角度 angle,根据鼠标位置调整角度,使封面元素随鼠标水平移动而旋转,并限制旋转角度在 -180 到 0 度之间。 使用 style.transform 属性将计算得到的旋转角度应用到封面元素,实现立体效果。 通过 console.log(e.pageX) 输出鼠标横坐标位置。

实现效果图(可翻阅)

微信截图_20240421114952.png

项目小结

完成这个小项目后,有没有感觉自己就像是一位魔术师,把一本普通的书变成了一个会动的对象!虽然并没有让书飞起来或者变出兔子,但能让书封面随着鼠标的动作而自如地翻转,也是一种小小的魔法吧!

在编写代码的过程中,我们学到了如何使用JavaScript来捕捉鼠标事件,并根据鼠标位置的变化来实现元素的旋转效果。虽然刚开始可能有些困难,但通过不断调试和尝试,最终成功实现了预期的效果。

最重要的是,通过这个项目,我们更深入地理解了前端开发中的交互性设计,以及如何通过简单的动画和交互来增加用户体验。总的来说,虽然是个小项目,但收获颇丰,让我们更加热爱前端开发这个神奇的领域!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

和后端吵架后,我写了个库,让整个前端团队更加规范!

发表于 2024-04-24

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心~

本文源码地址:github.com/sanxin-lin/…

背景

在平时的开发中,表格数据->(增加/编辑/查看)行->(增加/编辑)提交,这是很常见且简单的业务,但是就是这些业务,我也发现一些问题

首先我们来理性一下这些业务的逻辑

  • 第一步:请求回表格的数据
  • 第二步:点开(增加/编辑/查看)弹窗,如果是(编辑/查看),则需要将表格行的数据传到弹窗中回显
  • 第三部:如果是(编辑)弹窗,则需要把表单数据提交请求接口

我用一个图来概括大概就是:

问题所在

我不知道其他公司怎么样,但是就拿我自身来举例子,公司的后端跟前端的命名规则是不同的

  • 后端命名: 请求方法+字段类型+字段含义+下划线命名(比如 in_name、os_user_id)
  • 前端命名: 字段含义+驼峰命名(比如 name、userId)

回到刚刚的业务逻辑,还是那张图,假如我们前端不去管命名的话,那么数据的传输是这样的,发现了很多人都懒得去转换后端返回的字段名,直接拿着后端的字段名去当做前端的表单字段名,但这是不符合前端规范的

理想应该是表单要用前端的命名,比如这样

但是很多前端就是懒得去转换,原因有多个:

  • 开发者自身比较懒,或者没有规范意识
  • 回显时要转一次,提交时还要再转一次,每次总是得写一遍

解决方案

所以能不能写一个工具,解放开发者的压力又能达到期望的效果呢?比如我开发一个工具,然后像下面这样在弹窗里用

  • state: 响应式表单数据,可以用在弹窗表单中
  • resetState: 重置表单
  • inputState: 将表格行数据转成表单数据
  • outputState: 将表单数据转成提交请求的数据

配置的含义如下:

  • default: 表单字段默认值
  • input: 转入的字段名
  • output: 转出的字段名
  • inputStrategy: 转入的转换策略,可以选择内置的,也可以自定义策略函数
  • outputStrategy: 转出的转换策略,可以选择内置的,也可以自定义策略函数

转入和转出策略,内置了一些,你也可以自定义,内置的有如下

下面是自定义策略函数的例子,必须要在策略函数中返回一个转换值

这样的话,当我们执行对应的转换函数之后,会得到我们想要的结果

use-dsp

所以我开发了一个工具

源码地址:github.com/sanxin-lin/…

其实 dsp 意思就是

  • data
  • state
  • parameter
1
2
3
4
5
ts复制代码npm i use-dsp
yarn i use-dsp
pnpm i use-dsp

import useDSP from 'use-dsp'

为啥不从一开始就转?

有人会问,为啥不从一开始请求表格数据回来的时候,就把数据转成前端的命名规范?

其实这个问题我也想过,但是设想一下,有一些表格如果只是单纯做展示作用,那么就没必要去转字段名了,毕竟不涉及任何的数据传递。

但是需要编辑或者查看弹窗的表格,就涉及到了行数据的传递,那么就需要转字段名

结语 & 加学习群 & 摸鱼群

我是林三心

  • 一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手;
  • 一个偏前端的全干工程师;
  • 一个不正经的掘金作者;
  • 一个逗比的B站up主;
  • 一个不帅的小红书博主;
  • 一个喜欢打铁的篮球菜鸟;
  • 一个喜欢历史的乏味少年;
  • 一个喜欢rap的五音不全弱鸡

如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 –> 摸鱼沸点

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

AI菜鸟向前飞 — LangChain系列之六 - 深入浅出

发表于 2024-04-24

简介

LCEL是 LangChain Expression Language的缩写


先来做个程序书写的对比,观察一下、使用了Expression Language语法之后更加短小精悍(缩短了一倍长度,更加高级的用法缩短会更多)而且代码看起来更加简单易读。
  • 使用EL
1
2
3
4
5
javascript复制代码from langchain_community.chat_models import ChatOllama
from langchain.schema import HumanMessage
from langchain_core.output_parsers import StrOutputParser

(ChatOllama(model="llama3", temperature=0) | StrOutputParser()).invoke([HumanMessage(content="你好啊, AI小助手")])
  • 未使用EL
1
2
3
4
5
6
7
8
9
10
11
ini复制代码from langchain_community.chat_models import ChatOllama
from langchain.schema import HumanMessage
from langchain_core.output_parsers import StrOutputParser

chat = ChatOllama(model="llama3", temperature=0)

output_parser = StrOutputParser()

response = chat.invoke([HumanMessage(content="你好啊, AI小助手")])

output_parser.invoke(response)
Expression Language是一种将 **Runnable** 组成链 **(Runnables into chains)** 的声明性方式,以这种方式构建的任何链都将自动具有同步、异步、批处理、流支持等等,包含RunnableSequence 和 RunnableParallel两大部分内容。


    关于RunnableParallel内容,放到后面再介绍

RunnableSequence:按顺序调用一系列可运行对象,一个可运行对象的输出充当下一个可运行对象的输入,使用管道符号(即“|”)来分割两个对象。

1
2
3
arduino复制代码chain = prompt | model | output_parser

chain.invoke({"text": "你好啊, AI小助手"})

用一张图简单描绘这流式传输吧:

图片

把上图的组成统一成一个Chain(就是LangChain的“Chain”)

按步骤解析每个可运行对象的处理的过程和内容:

image.png

首先, 让我们看一下代码的完整执行过程

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码from langchain_community.chat_models import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("{text}")
model = ChatOllama(model="llama3", temperature=0)
output_parser = StrOutputParser()

chain = prompt | model | output_parser

response = chain.invoke({"text": "你好啊, AI小助手"})

print(response)

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
ruby复制代码[chain/start] [1:chain:RunnableSequence] Entering Chain run with input:
{
"text": "你好啊, AI小助手"
}
[chain/start] [1:chain:RunnableSequence > 2:prompt:ChatPromptTemplate] Entering Prompt run with input:
{
"text": "你好啊, AI小助手"
}
[chain/end] [1:chain:RunnableSequence > 2:prompt:ChatPromptTemplate] [0ms] Exiting Prompt run with output:
[outputs]
[llm/start] [1:chain:RunnableSequence > 3:llm:ChatOllama] Entering LLM run with input:
{
"prompts": [
"Human: 你好啊, AI小助手"
]
}
[llm/end] [1:chain:RunnableSequence > 3:llm:ChatOllama] [5.28s] Exiting LLM run with output:
{
"generations": [
[
{
"text": "😊 你好啊!我是你的AI小助手,欢迎你来到这里!有什么问题或需求,请随时问我,我会尽力帮助你。😊",
"generation_info": {
"model": "llama3",
"created_at": "2024-04-23T17:37:18.840986487Z",
"message": {
"role": "assistant",
"content": ""
},
"done": true,
"total_duration": 5271991298,
"load_duration": 1043179188,
"prompt_eval_count": 18,
"prompt_eval_duration": 1174320000,
"eval_count": 40,
"eval_duration": 2919644000
},
"type": "ChatGeneration",
"message": {
"lc": 1,
"type": "constructor",
"id": [
"langchain",
"schema",
"messages",
"AIMessage"
],
"kwargs": {
"content": "😊 你好啊!我是你的AI小助手,欢迎你来到这里!有什么问题或需求,请随时问我,我会尽力帮助你。😊",
"tool_calls": [],
"invalid_tool_calls": []
}
}
}
]
],
"llm_output": null,
"run": null
}
[chain/start] [1:chain:RunnableSequence > 4:parser:StrOutputParser] Entering Parser run with input:
[inputs]
[chain/end] [1:chain:RunnableSequence > 4:parser:StrOutputParser] [0ms] Exiting Parser run with output:
{
"output": "😊 你好啊!我是你的AI小助手,欢迎你来到这里!有什么问题或需求,请随时问我,我会尽力帮助你。😊"
}
[chain/end] [1:chain:RunnableSequence] [5.28s] Exiting Chain run with output:
{
"output": "😊 你好啊!我是你的AI小助手,欢迎你来到这里!有什么问题或需求,请随时问我,我会尽力帮助你。😊"
}
😊 你好啊!我是你的AI小助手,欢迎你来到这里!有什么问题或需求,请随时问我,我会尽力帮助你。😊

上一个的输出就是下一个的输入

再分析, 整个Pipeline中每一个可运行的对象,其底层的jsonschema逻辑是什么?(绘制了四张思维导图来说明)

Pasted image 20240424014436.png

Pasted image 20240424014514.png

Pasted image 20240424014538.png

Pasted image 20240424014602.png

还剩余“前后”两个,即:

  • 整体Pipeline Chain输入的Schema(即:prompt的输入的json schema)
1
2
3
4
5
6
7
json复制代码{"title": "PromptInput",
"type": "object",
"properties": {
"text":
{"title": "Text", "type": "string"}
}
}
  • 整体Pipeline Chain输出的Schema(即:output_parser的输出的json schema)
1
json复制代码{"title": "StrOutputParserOutput", "type": "string"}

To be continue…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

关于ffmpeg基础知识入门 关于ffmpeg 怎样使用ff

发表于 2024-04-24

关于ffmpeg

关于ffmpeg,我们首先需要知道它是什么,ffmpeg官网对ffmpeg进行了定义:

image.png

一个针对音视频的录制/转换/串流传输的跨平台解决方案。

image.png

当然,在about页面介绍得更加详细,可以说ffmpeg是针对音视频领域的全能操作框架。在现如今的互联网世界中,关于音视频的操作处理,或直接或间接都会依赖到这个框架(当然也有其他音视频框架)。

怎样使用ffmpeg

ffmpeg有两种使用方法:

  • 自定义开发使用
  • 命令行使用

因为ffmpeg的音视频处理能力都封装在对应的库文件中,因此我们再可以在自己的代码工程中嵌入对应的库文件,然后就可以调用它的基础能力,通过各种组合实现我们想要的功能,这就是自定义开发使用。

而且ffmpeg自身也会把这些功能库统一封装并套壳,打包成一个可执行文件,可以接受各种输入命令,比如在windows上就是ffmpeg.exe文件,我们可以下载这个可执行文件,然后再命令行窗口执行对应的命令行代码。

命令行

我们以windows平台为例,在官网下载最新的可执行文件,不出意外,你会下载到三个可执行文件,分别是:

  • ffmpeg,用于音视频进行修改(录制,采集,裁剪,转换等)处理的能力
  • ffprobe,用于分析音视频文件的相关信息
  • ffplay,发播放音视频

把文件所在路径添加到系统路径即可在命令行窗口进行调用。

比如查看媒体文件的一些信息

1
ffprobe -show_streams sample.mp4 // 显示sample.mp4内部的流的信息

比如视频文件的格式转换

1
2
// mp4转换为avi
ffmpeg -i sample.mp4 -c copy output.avi

命令行的参数和组合形式非常多,具体可以参考官网 Command Line Tools Documentation。

也可以先看看阮一峰 FFmpeg 视频处理入门教程,我认为他的教程都非常详细且易懂

自定义开发

自定义开发则是撇开了ffmpeg的壳,只获取其内部功能实现的库文件,把库文件集成到我们工程中,通过调用ffmpeg提供的API来实现我们自己的功能,这个会涉及到了c/cpp开发。

关于自定义开发更多细节就不举例了,这不是本文的重点,而且后面会专门在ffmpeg的基础上进行功能开发。

小结

相对而言命令行的方式更简单一些,因为命令行相当于已经提前封装了一些功能实现的逻辑代码,自定义开发的使用复杂的多,因为自定义开发则需要自己实现逻辑。但是自定义开发比较灵活,而且更适合精细化的,自定义程度较高的那些需求,而且更容易让我们理解音视频处理的一些过程。

其实这两种使用方式没有本质区别,命令行在library之上增加了中间层,把一些功能实现凝结为几个命令参数以达到简化开发的目的。而自定义则直接拿开中间层,直接接触library。

image.png

ffmpeg的功能模块

image.png

  • libavcodec 提供音视频的编解码能力
  • libavformat 提供处理各种音视频容器格式的能力,比如封装,解封装
  • libavutil 提供一些实用的功能函数
  • libavfilter 提供音视频流的滤镜效果
  • libavdevice 提供操作输入和输出设备的能力。例如,从摄像头获取视频数据以及将视频数据输出到屏幕
  • libswresample 实现音频混合和重采样能力
  • libswscale 实现视频帧的颜色转换和缩放能力

如果使用命令行模式,一般会把上述的库全部打包起来,而如果是自定义开发则是按需使用,比如如果不涉及设备操作可以不要libavdevice,不需要滤镜功能可以不要libavfilter…

这些支持库都是跨平台的,既可以是动态库也可以是静态库,

ffmpeg的一些基础概念

time_scale

time_scale 可以称作时间刻度,具体的含义是把一秒钟分为多少个刻度。

1
2
3
time_scale = 10 // 表示把一秒钟分为10份,10个刻度,每个刻度代表1/10秒

time_scale = 1000 // 表示把一秒钟分为1000份,1000个刻度,每个刻度代表1/1000秒

time_base

time_base是ffmpeg中一个非常重要的概念,一般称作时间基,或者可以说是ffmpeg中的时间基础单位。

现实世界所使用的时间基础单位一般是是秒,因为秒对于普通人而言已经足够精确了,使用毫秒或者微秒毫无必要,但是在ffmpeg中不是以秒为基础单位,而是把秒分为若干份,以一份作为ffmpeg中的时间基础单位。

1
2
3
time_base = 1/24   //表示把1s分为24份,以1/24作为ffmpeg的时间的基础单位。

time_base = 1/1000 //表示把1s分为1000份,以1/1000作为ffmpeg的时间的基础单位。

ffmpeg中大量的时间表示(AVStram,AVPacket,AVFrame中的PTS,DTS),都是以time_base作为基础单位的,而不是现实时间。

PTS/DTS/pts_time

  • DTS(Decoding Time Stamp,解码时间戳)
+ 指示解码器应该在什么时间点开始解码该帧(解码顺序)
  • PTS(Presentation Time Stamp,显示时间戳)
+ 指示帧应该在什么时间点被呈现给用户。PTS 是在解码后确定的时间戳,用于视频帧或音频帧的渲染或播放顺序。
  • pts_time
+ 当前媒体帧的显示时间,单位为秒

DTS,PTS一般都是以time_base为单位的。

关于pts,pts_time,time_base之间的计算方式

1
2
3
4
5
6
time_base = 1/75; 
Frame pts pts_time
0 0 0 x 1/75 = 0.00 (表示该帧在第0秒显示)
1 3 3 x 1/75 = 0.04 (表示该帧在第0.04秒显示)
2 6 6 x 1/75 = 0.08 (表示该帧在第0.08秒显示)
3 9 9 x 1/75 = 0.12 (表示该帧在第0.12秒显示)

AVStream

在ffmpeg中,读取或者写入视频文件时,内部会构建一个数据结构AVFormatContext,这个结构中包含了视频的相关信息,其中一个信息就是AVStram数组,每一个AVStream都包含了一种数据,比如音频和视频分别代表一个AVStram结构,我们可以从中获取比如时长,帧数,编解码器信息,time_base等

image.png

一旦我们获取了AVStream,我们就可以读取该Stream所代表的数据。

AVPacket

在ffmpeg中,在视频文件读取到基本信息之后,就可以读取正式数据内容用于解码,而这个用于解码的数据格式就是AVPacket.

AVPakcet内部包括待一段解码的数据data,size,pts,dts,time_base,stream_id(表示packet读取自哪个AVStream)等。

image.png

AVFrame

AVFrame则是解码后的原始数据,表示一个可以被使用帧,注意这并不一定是视频帧,也可能是音频帧,因为视频和音频都使用AVFrame的数据结构。

AVFrame内包含可播放数据,宽,高(视频),帧类型(视频),采样数(音频),音频格式,PTS等

一般如果是视频,AVFrame表示得是YUV或者RGB图片;如果是音频,则AVFrame表示得是PCM数据。

image.png

此时的得到的数据可以直接被播放。

音视频同步

在获得了可用的音频或者视频帧之后,也并不是直接发送到对应的设备进行播放即可,一般需要我们进行音视频同步控制,因为会出现音频和视频因为各种差异导致获得可用帧的速度差别很大,比如视频很快播放完,但是音频才播了一点点。因此我们需要通过PTS和time_base来控制每一帧的播放速度。

至于同步方法,一般是需要建立一个时间坐标系作为参考,然后计算每一帧的显示时间戳是否对应了正确的时间,否则就等待。

逻辑流程与数据格式转换

根据上面的对一些数据类型的简单介绍,我们已经可以得到一个音频文件播放过程的逻辑流程了

image.png

同样的,把可播放的原始数据保存成文件则是这个过程的逆过程,大同小异。

总结

本文主要对ffmpeg使用方法,核心模块,ffmpeg中的一些基本概念做了介绍,后面将会站在自定义开发的角度上,分析ffmpeg的数据结构的详情,和一个视频被播放的完整过程实现,最终会讲讲在Android平台的使用方式。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Android Studio运行Java/Kotlin Ap

发表于 2024-04-24

前言

在Android开发中,有时候需要快速验证与Android无关的纯Java/Kotlin代码,这个时候可能会选择直接在Android工程中某个类中写个测试代码,然后Gradle编译,运行看结果。或者是直接开启IntelliJ 直接开撸。我更习惯于在Android工程中添加个测试的module,然后编写。最近使用Android Studio Hedgehog | 2023.1.1 + Kotlin 1.9.22 按照以前的方式,发现了编译问题,费了点时间解决。

本篇文章,你可以get到什么?

Android Studio中运行纯Java/Kotlin代码

解决编译错误

Android Studio运行Application/Kotlin

  1. Android Studio -> File -> New -> New Module

image.png

  1. Java/Kotlin Library

以我创建的purekotlin 模块为例,该模块会自动生成,以及项目级别的settings build文件会有变化。

1
2
3
java复制代码===> settings.gradle.kts

include(":purekotlin")
1
2
3
4
5
6
7
8
9
10
11
bash复制代码===> purekotlin module -> build.gradle.kts

plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
}

java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}

增加启动配置

添加Java/Kotlin文件后,写个Hello World, 需要更改Configuration, 默认是app

image.png

image.png

Application对应Java程序, Kotlin对应Kotlin程序。

Kotlin 运行配置

image.png

Java 运行配置

image.png

Java的Application配置跟Kotlin稍微有差异。

编译错误

按照上面的配置,写好简单的hello world,发现无法编译,提示:

Execution failed for task ‘:purekotlin:compileKotlin’.
Inconsistent JVM-target compatibility detected for tasks ‘compileJava’ (1.7) and ‘compileKotlin’ (17).

解决编译问题

直接把新建module下的

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码===> purekotlin module -> build.gradle.kts

plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
}

// ==============删除 或者 注释=================
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}

git ignore

若是经常去添加,删除这个测试module, 会很累赘, 有时候还不小心把该测试module git add了,编辑.gitignore文件在其中加入 /purekotlin 这样,该文件就会被git忽略。 记得测试完,恢复

1
2
3
java复制代码===> settings.gradle.kts

include(":purekotlin")

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

明日方舟游戏助手:一键完成日常任务 开源日报 No23

发表于 2024-04-24

picture

MaaAssistantArknights/MaaAssistantArknights

Stars: 11.6k License: AGPL-3.0

picture

MaaAssistantArknights 是一款《明日方舟》游戏的小助手,基于图像识别技术,支持一键完成全部日常任务。

  • 刷理智、掉落识别及上传企鹅物流
  • 智能基建换班、自动计算干员效率
  • 自动公招、支持手动识别公招界面
  • 支持识别干员列表和养成材料,并可导出至其他工具进行规划和计算
  • 一键全日常自动长草等功能
  • 提供多语言支持,并有详细的使用说明和外服适配教程

SDWebImage/SDWebImage

Stars: 24.8k License: MIT

picture

SDWebImage 是一个具有缓存支持的异步图像下载器,同时也可以作为 UIImageView 的类别。
该项目提供了以下功能和优势:

  • 为 UIImageView、UIButton、MKAnnotationView 添加了 Web 图像和缓存管理的类别
  • 异步图像下载器
  • 带有自动缓存过期处理的异步内存+磁盘图像缓存
  • 后台图像解压以避免帧率下降
  • 渐进式加载图片(包括在 Web 浏览器中显示的 GIF 动画图片)
  • 缩略图解码以节省大型图片所需的 CPU 和内存资源
  • 可扩展的图片编码器,支持诸如 WebP 等多种格式
  • 全栈解决方案,保持 CPU 和内存在动画图片之间平衡

此外还包括:

  • 自定义可组合变换,下载后立即对其进行修改
  • 多个自定义高速快取系统
  • 多个装载机制系统来扩展功能, 如 Photos Library
  • 图片加载指示符
  • 图片加载转场动画

Snapchat/KeyDB

Stars: 9.6k License: BSD-3-Clause

picture

KeyDB 是一个性能高、专注于多线程、内存效率和高吞吐量的 Redis 分支。其特点包括主动复制、FLASH 存储和子键过期等功能,采用 MVCC 架构,允许执行 KEYS 和 SCAN 等查询而不会阻塞数据库并降低性能。与 Redis 协议、模块和脚本完全兼容,并保持与 Redis 开发同步,是现有部署的替代品。通过 Active-Replication 实现热备故障转移简化,可在副本间轻松分配写入并使用简单的基于 TCP 的负载平衡/故障转移。相比于 Redis,在相同硬件上 KeyDB 可以实现显著更高的吞吐量,减少运营成本和复杂性。

  • 高性能:重点优化了多线程处理
  • 内存效率:提供 FLASH 存储选项
  • 全面兼容:保持与 Redis 协议一致
  • 主动复制:实现热备份故障转移

OpenInterpreter/01

Stars: 2.6k License: AGPL-3.0

picture

01 是一个开源的语言模型计算机。该项目旨在构建一个开放源代码生态系统,用于 AI 设备。
其主要功能和核心优势包括:

  • 可以为类似 Rabbit R1、Humane Pin 或 Star Trek 计算机等对话设备提供动力的旗舰操作系统。
  • 通过保持开放性、模块化和免费,意图成为这一领域的 GNU/Linux。
  • 提供软件和硬件支持,如安装依赖项、运行仿真器等。
  • 提供自定义功能,并介绍了 LMC 消息格式和动态系统消息等协议。

huggingface/safetensors

Stars: 2.3k License: Apache-2.0

picture

safetensors 是一个简单、安全的存储和分发张量的方式。

  • 实现了一种新的简单格式,用于安全地存储张量(与 pickle 相反),并且仍然快速(零拷贝)。
  • 提供 Python 和 Rust 的支持。
  • 允许存储空张量和标量。
  • 采用小端序,行优先顺序,并具有灵活性、控制布局等特点。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Linux设备驱动系列(三)——参数传递 1 模块参数宏 2

发表于 2024-04-24

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/ach-ex8gq…

在C语言应用程序中,可以通过在main函数定义中添加argc和argv参数来获取用户的输入。类似的,Linux设备驱动程序同样可以传递参数。本文将介绍Linux设备驱动程序中参数定义以及使用方法。

1 模块参数宏

Linux头文件”linux/module.h”提供了多个宏来声明内核模块的参数及其读写权限。

  • S_IWUSR、S_IRUSR、S_IXUSR;
  • S_IRGRP、S_IWGRP、S_IXGRP。

其中,R/W/X分别表示读、写和执行权限,USR/GRP分别表示用户和用户组。

允许通过”|”运算符来同时设置多个权限。

下面将简要介绍模块参数宏的使用方法,这些宏都在”linux/moduleparam.h”中定义。

1.1 module_param()

module_param()宏有三个参数:参数名,参数类型,以及参数权限掩码。

1
scss复制代码 module_param(name, type, perm);

定义的模块参数可以在”/sys/module/<module_name>/parameters/“下面找到。

1
2
scss复制代码 // /sys/module/my_driver/parameters/my_param
 module_param(my_param, int, S_IWUSR|S_IRUSR);

模块参数类型有以下几种:

  1. 布尔类型:bool,invbool;
  2. 字符指针类型:charp;
  3. 基本整型类型:int,long,short,uint,ulong,ushort。

1.2 module_param_array()

module_param_array()宏用于声明以”,”分隔的数组类型的参数:

1
scss复制代码 module_param_array(name, type, num, perm);

1.3 module_param_cb()

module_param_cb()宏用于注册一个回调函数,当参数取值改变时被调用。最常见的应用场景是在内核模块运行期间动态修改其配置。

如果用module_param_cb()声明一个具有可写权限的模块参数,当内核模块插入内核后,可以通过网对应的sysfs文件写入值来修改模块参数的取值,例如”echo 1 > /sys/module/my_driver/parameters/my_param”,那么注册的回调函数将会被调用。

1
2
3
4
5
6
7
8
c复制代码 struct kernel_param_ops
 {
   int (*set)(const char *val, const struct kernel_param *kp);
   int (*get)(char *buffer, const struct kernel_param *kp);
   void (*free)(void *arg);
 };
 
 module_param_cb(name, ops, param_ptr, perm);

module_param_cb()接收一个struct kernel_param_ops类型的参数作为模块参数的回调函数。

2 示例:Linux设备驱动参数传递

下面是一个简单的Linux设备驱动参数传递的示例代码,定义了4个模块参数:

module_param.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
arduino复制代码 #include<linux/kernel.h>
 #include<linux/init.h>
 #include<linux/module.h>
 #include<linux/moduleparam.h>
 
 int int_param, arr_param[4];
 char *str_param;
 int cb_param = 0;
 
 module_param(int_param, int, S_IRUSR|S_IWUSR);
 module_param(str_param, charp, S_IRUSR|S_IWUSR);                     
 module_param_array(arr_param, int, NULL, S_IRUSR|S_IWUSR);
 
 int notify_param(const char *val, const struct kernel_param *kp)
 {
   int res = param_set_int(val, kp);
   if(res == 0) {
     pr_info("New value of cb_param = %d\n", cb_param);
     return 0;
  }
   return -1;
 }
 const struct kernel_param_ops my_param_ops =
 {
  .set = &notify_param,
  .get = &param_get_int,
 };
 module_param_cb(cb_param, &my_param_ops, &cb_param, S_IRUGO|S_IWUSR );
 
 static int __init hello_world_init(void)
 {
   int i;
   pr_info("int_param = %d\ncb_param = %d\nstr_param = %s\n",
          int_param, cb_param, str_param);
   for (i = 0; i < ARRAY_SIZE(arr_param); i++)
     pr_info("arr_param[%d] = %d\n", i, arr_param[i]);
   pr_info("Kernel Module Inserted!\n");
   return 0;
 }
 
 static void __exit hello_world_exit(void)
 {
     pr_info("Kernel Module Removed!\n");
 }
 
 module_init(hello_world_init);
 module_exit(hello_world_exit);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("feifei <feifei@gmail.com>");
 MODULE_DESCRIPTION("A simple linux device driver");
 MODULE_VERSION("1.0");

说明:如果不特别注明,示例代码均采用前文的示例Makefile文件(修改相应的.o文件名即可),这里不再重复给出。

最后编译运行,输出如图:

image-20240423232537367

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/ach-ex8gq…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…252627…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%