编程

编程

为什么不建议在代码中使用 User 这个单词?

科技创新王叫兽 发表了文章 • 0 个评论 • 76 次浏览 • 2021-05-14 15:24 • 来自相关话题

当你意识到你在项目开始时做的轻量、简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上。现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预期,如果一开始就用正确的方式来做,就不会发生这样的事。今天,我要... ...查看全部

当你意识到你在项目开始时做的轻量、简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上。现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预期,如果一开始就用正确的方式来做,就不会发生这样的事。

今天,我要告诉你的是一个经常犯的错误,一个会给你带来无穷无尽的问题的单词,那就是“users”。

这个单词有两个最基本的错误:

1.对你的需求来说 “User” 几乎从来都不是一个好的描述。2.“User” 会导致一个基本的设计安全缺陷。“user” 的概念是模糊不清的,使用更精准的术语几乎总是能起到更好的效果。

你没有使用者

最开始,没有任何一个软件系统真的有使用者存在。乍一看“user”是一个好的描述,但是你稍微一想就会意识到你的业务逻辑实际上比这要复杂的多。我会使用三个例子,从一个极端的情况出发。机票预订系统没有“users” 我曾经给机票预订系统写过访问控制逻辑,下面只是一小部分需求:

1.旅客可以使用预定记录码通过网站查看预定信息。

2.购买者可以通过信用卡号后四位数在网站上修改预订信息。

3.旅行社可以查看和修改他们的预订。

4.航空公司的值机人员可以根据角色和航空公司来查看和修改预订信息,这需要旅客提供身份信息。

不再一一列举。一些与人类相关的基本概念是“旅客”,“代理”(网站也可是看作代理)和“购买者”。“user”这个概念根本没用,并且在许多请求中我根本不会使用这个单词,举个例子,我们的请求必须包括旅客和代理人的证件,而不是使用者的证件。

Unix 没有 “users”

我们看一个不太一样的例子。Unix (这些天被称为POSIX)有用户,他们可以登录并执行代码。这样看起来很不错吧?我们深入看一下。

如果我们把所有都当作“users”的话,我们将会有:1.使用终端或者图形界面登录的人 2.像邮件或者web服务器这种系统服务也会以“users”的身份运行,例如nginx可以以httpd用户运行。3.在服务器上经常会有多人共享一个管理员账号用来SSH登录(例如,亚马逊的Ubuntu虚拟机默认SSH账号就是‘ubuntu’) 5.root 身份,和上面其他身份都不同。

上面四个是几乎不同的概念,但是在POSIX上他们都是 “users”. 一会儿我们就会看到,把这些概念都称为‘user’会导致很多安全问题。

在操作上,因为POSIX的用户模型边界存在,我们甚至不能找到一种方式说“只能让 Alice 和 Bob 通过这个账号登录”。SaaS 服务提供商没有 “users”

Jeremy Green 最近就用户模型在SaaS中的应用在推特上发文,它第一次提醒了我写下这篇文章,他的基本观点是

SaaS 服务几乎总是:

1.某个组织中的一个人支付服务费用。2.一个或多个人共同使用这个服务。如果你一开始就把这些人作为一个用户,你将会陷入一个痛苦的世界。你无法建立团队模型,你无法组建同时为多人支付的模型,然后你就会开始改造你的系统。现在你在SaaS案例中学到了一课,我们来看一看你的生活。

但是这只是众多例子中的一个:“users”的概念太模糊了。如果你开始怀疑“user”这个词,最终你可能发现最终你其实只需要两个概念:团队(用来组织关系和支付)和成员(实际使用服务的人)。“Users” 是一个安全问题 “user” 这个单词不仅是业务逻辑的问题,它也导致了一系列安全问题。

“user” 这个单词如此的模糊以至于从根本上将两个概念合并了:1.一个人。2.他们在软件中的代表性。

为了说明这个问题,假设你正在访问一个居心不良的网站,在它服务器上的图片导致了你的浏览器内存溢出。远程网站控制着你的浏览器,并且开始将你的文件上传到他的服务上。

为什么它能这样做?

因为浏览器是以系统用户的身份运行的,它被认为与人类身份的你相同,实际上你们是不同的。你作为’user’,不想上传文件。但是系统的账号也是‘user’,能够上传文件,如果浏览器运行在你的账号之下,他所有的行为会被当作是你的意图,也就是说是你让它这么做的,实际上不是。

这就是被称为Confused Deputy的问题。如果你使用“用户”这个词来描述两个根本不同的东西,那么这个问题就更有可能成为你设计的一部分。前期设计的价值

花更少的功夫处理相同的问题是成为高产程序员的关键。使用模糊不清的概念比如“用户”来组织你的软件,将会话费大量时间和精力来解决未来发生的问题。一上来就开始编码看起来是高产的,事实恰好相反。

下次你开始一个新的软件项目时,花几个小时预先确定你的术语和概念:你仍然不会完全正确,但你会做得更好。未来的你将感谢你所做的所有预防浪费的工作。

Geek Online 2020 编程挑战赛作品展示《Script Game》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 209 次浏览 • 2021-04-26 18:14 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:赚钱养家

作品:Script Game

源码地址:https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/46/files

  • Script Game的是一款线上剧本杀APP,它为喜欢剧本和推理的用户提供一个游戏平台。剧本杀是一种注重推理的互动桌面游戏,也是现代青年聚会的常玩娱乐之一。

  • 进入APP的玩家,可以选择不同的剧本,按照游戏流程轮流发言、使用道具,最终完成自己的目的、找到真正的凶手,而凶手则要隐藏自己,嫁祸他人。 在剧本杀中用户将作为一部故事中的角色,可以是侦探推理真凶,可以是古人恩爱情仇,浪荡天涯,可以是现代人心冷暖,在所扮演的角色中,体验到另一种人生。

  • 在Script Game 这个开放包容的APP中,用户可以体验不同的故事,也可以与不同的人们一起扮演不同的角色。

目录

  • APP功能及使用介绍

  • 用到的技术

APP功能及使用介绍

1.剧本杀房间

  • 用户可以选择是否成为一个主持人(组织者)的身份,并且进行剧本杀流程的把控。

  • 每次游戏开始存在人数限制,超出限制的用户可以选择等待或者旁观。

2.剧本选择

  • 不同房间有不同的剧本,用户根据自己想法选择封闭式玩法或者开放式玩法

3.创建剧本

  • 用户可以根据自己需求创建剧本,并且建立房间,成为房间的管理者。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Sing Together》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 210 次浏览 • 2021-04-26 17:46 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:生活不易

作品:Sing Together

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/23/files

  • Sing Together 为各位音乐爱好者提供一个共同K歌的机会。

  • 在这里每个人都可以是“蒙面歌王”,共同体会音乐的魅力,为自己喜欢的歌声打call。

目录

  • APP功能及使用介绍

  • 用到的技术

APP功能及使用介绍

1.K歌室

  • 进入K歌室以后,就可以进行在线K歌功能,在线进行音乐风暴狂欢。用户可选身份有:

    • 主持人

    • 普通K歌

    • 听众

2.粉丝互动

  • Sing Together的平台支持一位主播与多名粉丝的互动活动,粉丝可以为主播点赞、赠送礼物。

3.在线交友

  • 在K歌室中,每个用户之间可以进行互动,通过互动来增加彼此交流。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Warm Heart》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 159 次浏览 • 2021-04-26 17:05 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。


https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:曈塔天线

作品:Warm Heart

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/26/files

  • WarmHeart的开发目标是为人们提供一个情感碰撞平台,人们可以在这里分享自己的喜怒哀乐。在遭受挫折和迷茫时,能够通过WarmHart的暖心室获得他人的温暖和指导。

  • 随着生活节奏的加快,现代人的压力增大,而WarmHeart的主要目标之一是发挥语言的力量,为人们提供心理慰藉。

  • 在WarmHeart可以遇见温暖的普通陌生人,也可以找到专业的情感大师。在每个暖心室里面,都会常驻一位心理咨询师作为主持人,在分享经验的同时还能帮助人们解决心理问题。

  • 本程序为Warm Heart的Andriod版本。

目录

006vbfFply1gjmy81tm7rj30u01pkabg.jpg

APP功能及使用介绍

1.通过手机号注册用户

  • 用户通过手机号,获取验证码即可登录,开启旅程。 

  • 006vbfFply1gjmy8g0h9rj30u01t0js8.jpg

2.暖心畅聊室

  • 这是大家进行交心的地方,由主持人控制房间的情况,每个房间有除主持人外有8个发言位置。

    • 聊天社交

    • 互送礼物

    • 心理咨询

    • 个人分享

    • 更多功能可自行挖掘 

    • 006vbfFply1gjmy91yoz7j30i60msgnk.jpg

3.发布新话题

  • 如果对当前的畅聊室不满意,可以自主新建新的畅聊室。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Werewolf》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 155 次浏览 • 2021-04-26 16:40 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:NCC_621

作品:Werewolf

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/24/files

  • Werewolf的开发目标是为人们提供一个在线狼人杀游戏平台。

  • 在Werewolf中,玩家可以在线开始狼人杀游戏,每个玩家都会被分配到一个身份,每个身份都起到不同的作用。

  • 游戏分为两大阵营:狼人阵营和好人阵营。而胜利条件有多种:所有狼人出局,好人胜利;所有好人出局(屠城)狼人胜利以及所有平民出局或所有神职出局(屠边)狼人胜利。。

  • 在Werewolf中玩家可以自由选择游戏对象,还可以围观他人的游戏过程,体验推理和社交的快乐。

目录

APP功能及使用介绍

1.通过手机号注册用户

-用户通过手机号,获取验证码即可登录,开始游戏。

2.游戏房间

  • 用户进入房间之后,游戏由房间中担任上帝的玩家控制过程,其他成员有各自不同的身份。 --每一局狼人杀游戏都将从夜晚开始,每一个夜晚狼人可以睁眼并共同猎杀场上的一位玩家。夜晚之后将会进入白天,白天是所有玩家发言和投票的环节。 --每个白天场上存活的玩家将按顺序依次进行发言,在所有玩家发言结束后,法官将会组织一次放逐公投,在放逐公投中每一位玩家都拥有一票的投票权,玩家可以选择把票投给自己心目中所认为的狼人。最终,得票数最多的玩家将会被放逐出局并留下“遗言”。 --“遗言”之后游戏将会进入下一个夜晚,狼人又可以再一次睁眼选择一位玩家进行猎杀,如此“黑夜—白天—黑夜”循环,直到某一方阵营达成了胜利条件,则游戏结束。

  • 玩家可以在房间中进行游戏,也可以旁观他人的游戏,也可以创建自己的游戏房间。

 006vbfFpgy1gjmxwh6424j30u01t0jsa.jpg006vbfFpgy1gjmxxaegutj30u01t0taq.jpg

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

如果再给我一次机会,我希望我从未学过编程

好玩创意徐凤年 发表了文章 • 0 个评论 • 122 次浏览 • 2020-11-04 14:39 • 来自相关话题

我是一名程序员。你可能会觉得我是专业的软件工程师,但实际上,软件工程不只是一个专业,还是一种生活方式。连帽衫、乒乓球、吃不完的零食和苏打水……都是这种生活的一部分。但虽然这个职业可以给人带来那么多回报,我还是要坦白一件事:有时我希望自己从来没有学过编程。怪癖自... ...查看全部

我是一名程序员。你可能会觉得我是专业的软件工程师,但实际上,软件工程不只是一个专业,还是一种生活方式。连帽衫、乒乓球、吃不完的零食和苏打水……都是这种生活的一部分。但虽然这个职业可以给人带来那么多回报,我还是要坦白一件事:

有时我希望自己从来没有学过编程。

怪癖

自从我开始学习编程以来,就再也不能像以前那样轻松地浏览网页了。我无法再平静地接受在各种网站上遇到的各种 bug。每当有奇怪的事情发生时,我的好奇心就上了头,然后就会打开浏览器开发工具开始调试网页。

尝试提交表单时出现加密错误消息?看到这样的错误,我会深入 JavaScript 控制台,查找错误消息,深入研究源代码,并仔细观察进进出出的网络请求。页面 UI 看起来有些过时,或者页面布局很漂亮?不管怎样,我都会调整浏览器窗口大小,检查页面响应屏幕尺寸的能力。什么?你竟然选择了 Comic Sans 字体?我可能永远不会再使用你或你们公司的产品了。

微信图片_20201104143349.jpg

请不要使用 ComicSans

职业生涯

觉得我的这些浏览习惯很怪异吗?这还不算啥,我和其他软件工程师的对话会让你印象更深刻的。总体而言,工程师往往固执己见。我们的血液中流淌着迂腐的基因。你喜欢使用空格还是 tabs 缩进代码?你更喜欢 Vim 还是 Emacs?Chrome 还是 Firefox?

聪明点的人会问到,这些对话“真的重要吗?”。Bikeshedding(在琐碎细节上浪费时间)是确实存在的现象,大家都需要经常反省。

微信图片_20201104143403.jpg

xkcd——真正的程序员

随着越来越多的公司采用敏捷方法,我们还得时常考虑在 Waterfall、Scrum 或 Kanban 开发生命周期之间权衡取舍。我们所有人都喜欢鄙视 Waterfall,毕竟这是老式的低效率公司所使用的遗留品。但是 Scrum 与看板之战还在继续。

你竟然是 Scrum 的信徒?我敢打赌,你为了满足那些 time box 会仓促提交混乱的代码,才能在冲刺结束之前赶上假想的时限。

微信图片_20201104143416.jpg

说到敏捷,故事的重点在哪里?时间尺度?努力?复杂性?风险?这些都有?你我可能会花费几小时时间来争论每个定义的优缺点,最后还是无法达成共识。

在某些时候,这种争论可能会演变为更多的 bikeshedding,但能够相互理解和有效沟通是至关重要的,其中就包括了对我们日常使用的术语达成共识。

前端开发带来的争论又是数不清的,其中最典型的是:“我们要到什么时候才会放弃对 Internet Explorer 的支持?”我必须在“讨厌 Internet Explorer,想要放弃它”和了解还在用它的客户需求之间找到完美的平衡点。

微信图片_20201104143428.jpg

持续学习

持续学习是每一位开发者必备的能力,因为技术形势日新月异。每月都有成百上千的新库和框架发布。JavaScript 倦怠不是什么幻觉,“学不动了”也不仅仅是调侃。

我得花一天的时间配置 Webpack、Rollup 和 Babel 以使用最新的 ES6+ 语法,而旁人听我这么说就像在听天书。Angular 和 AngularJS 截然不同?LitElement、Svelte 和 Stencil 似乎是很有前途的 Web 组件解决方案?Deno 可能是下一个 Node?听着都像是谜语。

当有人问我做什么工作时,我总会蹦出来一堆术语:“我通常使用前端技术,例如 HTML、CSS 和 JS。有时我必须使用 PHP 或 SQL,但我更多是 MEAN/MERN 栈开发人员。有时我使用 Heroku 之类的 PaaS 技术,还有时我会使用 AWS 或 GCP 之类的 IaaS 提供商。”

微信图片_20201104143437.jpg

个人生活

在业余时间里,我读的书干货十足,例如《干净代码》《重构》和《领域驱动设计》。我不是在看教科书,就是在阅读里面写着可疑建议的文章,或观看一些编程教程。关于编程的播客更能吸引人,这样我就能在路上听某人谈论写代码的方法,然后我又可以花一天时间谈相关话题了。

除了在线获取内容外,软件工程师还会花费大量个人时间来在网上创建内容。我个人的兴趣是构建简单的应用和游戏,其实没人看得到。这是浪费时间吗?也许是吧,但的确挺有意思的。


微信图片_20201104143447.jpg企业 BS 生成器应用

事实是

可事实是,编程给了我创造和创新的机会。它帮助我将创意变为现实,让我几乎从零开始构筑了很多东西。软件工程使我能够解决有趣而艰巨的挑战,理想情况下还能让人们的生活更轻松一些。编程使我的思维更具逻辑性。编程让我有机会不断学习,我还能以编程为职业获取薪水!

事实是,我喜欢编程。

参考阅读

https://hackernoon.com/i-wish-i-never-learned-to-code-7a1m3wwx

手把手教学|用代码写一个单机五子棋

IM即时通讯大神庵 发表了文章 • 0 个评论 • 195 次浏览 • 2020-10-14 16:26 • 来自相关话题

这篇文章主要为大家详细介绍了python实现单机五子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。# 简介这是实验室2018年底招新时的考核题目,使用Python编写一个能够完成基本对战的五子棋游戏。面向新手。程序主要包括两... ...查看全部

这篇文章主要为大家详细介绍了python实现单机五子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。

# 简介

这是实验室2018年底招新时的考核题目,使用Python编写一个能够完成基本对战的五子棋游戏。面向新手。

程序主要包括两个部分,图形创建与逻辑编写两部分。

程序的运行结果:

640.png

# 样式创建

老规矩,先把用到的包导入进来。

'''
@Auther : gaoxin
@Date : 2019.01.01
@Version : 1.0
'''

from tkinter import *
import math
12345678

然后建立一个样式的类,类名称chessBoard。这里加了很多注释,避免新手看不懂函数的作用,说实话我觉得挺别扭的。

#定义棋盘类
class chessBoard() :
    def __init__(self) :
     #创建一个tk对象,即窗口
        self.window = Tk()
        #窗口命名
        self.window.title("五子棋游戏")
        #定义窗口大小
        self.window.geometry("660x470")
        #定义窗口不可放缩
        self.window.resizable(0,0)
        #定义窗口里的画布
        self.canvas=Canvas(self.window , bg="#EEE8AC" , width=470, height=470)
        #画出画布内容
        self.paint_board()
        #定义画布所在的网格
        self.canvas.grid(row = 0 , column = 0)
    def paint_board(self) :
     #画横线
        for row in range(0,15) :
            if row == 0 or row == 14 :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 2)
            else :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 1)
        #画竖线
        for column in range(0,15) :
            if column == 0 or column == 14 :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 ,width = 2)
            else :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 , width = 1)
        #画圆
        self.canvas.create_oval(112, 112, 118, 118, fill="black")
        self.canvas.create_oval(352, 112, 358, 118, fill="black")
        self.canvas.create_oval(112, 352, 118, 358, fill="black")
        self.canvas.create_oval(232, 232, 238, 238, fill="black")
        self.canvas.create_oval(352, 352, 358, 358, fill="black")
1234567891011121314151617181920212223242526272829303132333435363738394041

# 逻辑编写


这里的主要看每个函数的功能就好了。

#定义五子棋游戏类
#0为黑子 , 1为白子 , 2为空位
class Gobang() :
    #初始化
    def __init__(self) :
        self.board = chessBoard()
        self.game_print = StringVar()
        self.game_print.set("")
        #16*16的二维列表,保证不会out of index
        self.db = [([2] * 16) for i in range(16)]
        #悔棋用的顺序列表
        self.order = []
        #棋子颜色
        self.color_count = 0
        self.color = 'black'
        #清空与赢的初始化,已赢为1,已清空为1
        self.flag_win = 1
        self.flag_empty = 1
        self.options()
    #黑白互换
    def change_color(self) :
        self.color_count = (self.color_count + 1 ) % 2
        if self.color_count == 0 :
            self.color = "black"
        elif self.color_count ==1 :
            self.color = "white"
    #落子
    def chess_moving(self ,event) :
        #不点击“开始”与“清空”无法再次开始落子
        if self.flag_win ==1 or self.flag_empty ==0  :
            return
        #坐标转化为下标
        x,y = event.x-25 , event.y-25
        x = round(x/30)
        y = round(y/30)
        #点击位置没用落子,且没有在棋盘线外,可以落子
        while self.db[y][x] == 2 and self.limit_boarder(y,x):
            self.db[y][x] = self.color_count
            self.order.append(x+15*y)
            self.board.canvas.create_oval(25+30*x-12 , 25+30*y-12 , 25+30*x+12 , 25+30*y+12 , fill = self.color,tags = "chessman")
            if self.game_win(y,x,self.color_count) :
                print(self.color,"获胜")
                self.game_print.set(self.color+"获胜")
            else :
                self.change_color()
                self.game_print.set("请"+self.color+"落子")
    #保证棋子落在棋盘上
    def limit_boarder(self , y , x) :
        if x<0 or x>14 or y<0 or y>14 :
            return False
        else :
            return True
    #计算连子的数目,并返回最大连子数目
    def chessman_count(self , y , x , color_count ) :
        count1,count2,count3,count4 = 1,1,1,1
        #横计算
        for i in range(-1 , -5 , -1) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        #竖计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        #/计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        #\计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        return max(count1 , count2 , count3 , count4)
    #判断输赢
    def game_win(self , y , x , color_count ) :
        if self.chessman_count(y,x,color_count) >= 5 :
            self.flag_win = 1
            self.flag_empty = 0
            return True
        else :
            return False
    #悔棋,清空棋盘,再画剩下的n-1个棋子
    def withdraw(self ) :
        if len(self.order)==0 or self.flag_win == 1:
            return
        self.board.canvas.delete("chessman")
        z = self.order.pop()
        x = z
        y = z//15
        self.db[y][x] = 2
        self.color_count = 1
        for i in self.order :
            ix = i
            iy = i//15
            self.change_color()
            self.board.canvas.create_oval(25+30*ix-12 , 25+30*iy-12 , 25+30*ix+12 , 25+30*iy+12 , fill = self.color,tags = "chessman")
        self.change_color()
        self.game_print.set("请"+self.color+"落子")
    #清空
    def empty_all(self) :
        self.board.canvas.delete("chessman")
        #还原初始化
        self.db = [([2] * 16) for i in range(16)]
        self.order = []
        self.color_count = 0
        self.color = 'black'
        self.flag_win = 1
        self.flag_empty = 1
        self.game_print.set("")
    #将self.flag_win置0才能在棋盘上落子
    def game_start(self) :
        #没有清空棋子不能置0开始
        if self.flag_empty == 0:
            return
        self.flag_win = 0
        self.game_print.set("请"+self.color+"落子")
    def options(self) :
        self.board.canvas.bind("<Button-1>",self.chess_moving)
        Label(self.board.window , textvariable = self.game_print , font = ("Arial", 20) ).place(relx = 0, rely = 0 ,x = 495 , y = 200)
        Button(self.board.window , text= "开始游戏" ,command = self.game_start,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=15)
        Button(self.board.window , text= "我要悔棋" ,command = self.withdraw,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=60)
        Button(self.board.window , text= "清空棋局" ,command = self.empty_all,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=105)
        Button(self.board.window , text= "结束游戏" ,command = self.board.window.destroy,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=420)
        self.board.window.mainloop()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171

最后,main函数

if __name__ == "__main__":
    game = Gobang()
1234

将以上的所有程序复制粘贴,即为完整的程序了,可以运行。


最后来一个完整程序,一个一个复制粘贴简直不要太麻烦。

'''
@Auther : gaoxin
@Date : 2019.01.01
@Version : 1.0
'''
from tkinter import *
import math
#定义棋盘类
class chessBoard() :
    def __init__(self) :
        self.window = Tk()
        self.window.title("五子棋游戏")
        self.window.geometry("660x470")
        self.window.resizable(0,0)
        self.canvas=Canvas(self.window , bg="#EEE8AC" , width=470, height=470)
        self.paint_board()
        self.canvas.grid(row = 0 , column = 0)
    def paint_board(self) :
        for row in range(0,15) :
            if row == 0 or row == 14 :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 2)
            else :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 1)
        for column in range(0,15) :
            if column == 0 or column == 14 :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 ,width = 2)
            else :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 , width = 1)
        self.canvas.create_oval(112, 112, 118, 118, fill="black")
        self.canvas.create_oval(352, 112, 358, 118, fill="black")
        self.canvas.create_oval(112, 352, 118, 358, fill="black")
        self.canvas.create_oval(232, 232, 238, 238, fill="black")
        self.canvas.create_oval(352, 352, 358, 358, fill="black")
#定义五子棋游戏类
#0为黑子 , 1为白子 , 2为空位
class Gobang() :
    #初始化
    def __init__(self) :
        self.board = chessBoard()
        self.game_print = StringVar()
        self.game_print.set("")
        #16*16的二维列表,保证不会out of index
        self.db = [([2] * 16) for i in range(16)]
        #悔棋用的顺序列表
        self.order = []
        #棋子颜色
        self.color_count = 0
        self.color = 'black'
        #清空与赢的初始化,已赢为1,已清空为1
        self.flag_win = 1
        self.flag_empty = 1
        self.options()
    #黑白互换
    def change_color(self) :
        self.color_count = (self.color_count + 1 ) % 2
        if self.color_count == 0 :
            self.color = "black"
        elif self.color_count ==1 :
            self.color = "white"
    #落子
    def chess_moving(self ,event) :
        #不点击“开始”与“清空”无法再次开始落子
        if self.flag_win ==1 or self.flag_empty ==0  :
            return
        #坐标转化为下标
        x,y = event.x-25 , event.y-25
        x = round(x/30)
        y = round(y/30)
        #点击位置没用落子,且没有在棋盘线外,可以落子
        while self.db[y][x] == 2 and self.limit_boarder(y,x):
            self.db[y][x] = self.color_count
            self.order.append(x+15*y)
            self.board.canvas.create_oval(25+30*x-12 , 25+30*y-12 , 25+30*x+12 , 25+30*y+12 , fill = self.color,tags = "chessman")
            if self.game_win(y,x,self.color_count) :
                print(self.color,"获胜")
                self.game_print.set(self.color+"获胜")
            else :
                self.change_color()
                self.game_print.set("请"+self.color+"落子")
    #保证棋子落在棋盘上
    def limit_boarder(self , y , x) :
        if x<0 or x>14 or y<0 or y>14 :
            return False
        else :
            return True
    #计算连子的数目,并返回最大连子数目
    def chessman_count(self , y , x , color_count ) :
        count1,count2,count3,count4 = 1,1,1,1
        #横计算
        for i in range(-1 , -5 , -1) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        #竖计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        #/计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        #\计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        return max(count1 , count2 , count3 , count4)
    #判断输赢
    def game_win(self , y , x , color_count ) :
        if self.chessman_count(y,x,color_count) >= 5 :
            self.flag_win = 1
            self.flag_empty = 0
            return True
        else :
            return False
    #悔棋,清空棋盘,再画剩下的n-1个棋子
    def withdraw(self ) :
        if len(self.order)==0 or self.flag_win == 1:
            return
        self.board.canvas.delete("chessman")
        z = self.order.pop()
        x = z
        y = z//15
        self.db[y][x] = 2
        self.color_count = 1
        for i in self.order :
            ix = i
            iy = i//15
            self.change_color()
            self.board.canvas.create_oval(25+30*ix-12 , 25+30*iy-12 , 25+30*ix+12 , 25+30*iy+12 , fill = self.color,tags = "chessman")
        self.change_color()
        self.game_print.set("请"+self.color+"落子")
    #清空
    def empty_all(self) :
        self.board.canvas.delete("chessman")
        #还原初始化
        self.db = [([2] * 16) for i in range(16)]
        self.order = []
        self.color_count = 0
        self.color = 'black'
        self.flag_win = 1
        self.flag_empty = 1
        self.game_print.set("")
    #将self.flag_win置0才能在棋盘上落子
    def game_start(self) :
        #没有清空棋子不能置0开始
        if self.flag_empty == 0:
            return
        self.flag_win = 0
        self.game_print.set("请"+self.color+"落子")
    def options(self) :
        self.board.canvas.bind("<Button-1>",self.chess_moving)
        Label(self.board.window , textvariable = self.game_print , font = ("Arial", 20) ).place(relx = 0, rely = 0 ,x = 495 , y = 200)
        Button(self.board.window , text= "开始游戏" ,command = self.game_start,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=15)
        Button(self.board.window , text= "我要悔棋" ,command = self.withdraw,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=60)
        Button(self.board.window , text= "清空棋局" ,command = self.empty_all,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=105)
        Button(self.board.window , text= "结束游戏" ,command = self.board.window.destroy,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=420)
        self.board.window.mainloop()
if __name__ == "__main__":
    game = Gobang()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

来源:https://blog.csdn.net/gaosanjin/article/details/108244164


融云 WebRTC 首帧显示优化策略到底有多强?

WebRTCadmin 发表了文章 • 0 个评论 • 173 次浏览 • 2020-09-29 17:47 • 来自相关话题

作者:融云 WebRTC 高级工程师 苏道音视频实时通话首帧的显示是一项重要的用户体验标准。本文主要通过对接收端的分析来了解和优化视频首帧的显示时间。流程介绍发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端... ...查看全部

timg.jpg

作者:融云 WebRTC 高级工程师 苏道


音视频实时通话首帧的显示是一项重要的用户体验标准。本文主要通过对接收端的分析来了解和优化视频首帧的显示时间。


流程介绍

发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端。接收端接收 RTP 包,取出 RTP payload,完成组帧的操作。之后音视频解码器解码帧数据,生成视频图像或音频 PCM 数据。

640.png


本文参数调整谈论的部分位于上图中的第 4 步。因为是接收端,所以会收到对方的 Offer 请求。先设置 SetRemoteDescription 再 SetLocalDescription。如下图蓝色部分:

2.png


参数调整

视频参数调整

当收到 Signal 线程 SetRemoteDescription 后,会在 Worker 线程中创建 VideoReceiveStream 对象。具体流程为 SetRemoteDescription

VideoChannel::SetRemoteContent_w 创建 WebRtcVideoReceiveStream。

WebRtcVideoReceiveStream 包含了一个 VideoReceiveStream 类型 stream_ 对象,通过 webrtc::VideoReceiveStream* Call::CreateVideoReceiveStream 创建。创建后立即启动 VideoReceiveStream 工作,即调用 Start() 方法。此时 VideoReceiveStream 包含一个 RtpVideoStreamReceiver 对象准备开始处理 video RTP 包。接收方创建 createAnswer 后通过 setLocalDescription 设置 local descritpion。对应会在 Worker 线程中 setLocalContent_w 方法中根据 SDP 设置 channel 的接收参数,最终会调用到 WebRtcVideoReceiveStream::SetRecvParameters。


WebRtcVideoReceiveStream::SetRecvParameters 实现如下:

void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetRecvParameters(
    const ChangedRecvParameters& params) {
  bool video_needs_recreation = false;
  bool flexfec_needs_recreation = false;
  if (params.codec_settings) {
    ConfigureCodecs(*params.codec_settings);
    video_needs_recreation = true;
  }
  if (params.rtp_header_extensions) {
    config_.rtp.extensions = *params.rtp_header_extensions;
    flexfec_config_.rtp_header_extensions = *params.rtp_header_extensions;
    video_needs_recreation = true;
    flexfec_needs_recreation = true;
  }
  if (params.flexfec_payload_type) {
    ConfigureFlexfecCodec(*params.flexfec_payload_type);
    flexfec_needs_recreation = true;
  }
  if (flexfec_needs_recreation) {
    RTC_LOG(LS_INFO) << "MaybeRecreateWebRtcFlexfecStream (recv) because of "
                        "SetRecvParameters";
    MaybeRecreateWebRtcFlexfecStream();
  }
  if (video_needs_recreation) {
    RTC_LOG(LS_INFO)
        << "RecreateWebRtcVideoStream (recv) because of SetRecvParameters";
    RecreateWebRtcVideoStream();
  }
}

根据上图中 SetRecvParameters 代码,如果 codec_settings 不为空、rtp_header_extensions 不为空、flexfec_payload_type 不为空都会重启 VideoReceiveStream。video_needs_recreation 表示是否要重启 VideoReceiveStream。重启过程为,把先前创建的释放掉,然后重建新的 VideoReceiveStream。以 codec_settings 为例,初始 video codec 支持 H264 和 VP8。若对端只支持 H264,协商后的 codec 仅支持 H264。SetRecvParameters 中的 codec_settings 为 H264 不空。其实前后 VideoReceiveStream 的都有 H264 codec,没有必要重建 VideoReceiveStream。可以通过配置本地支持的 video codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,并且判断 codec_settings 是否相等。如果不相等再 video_needs_recreation 为 true。这样设置就会使 SetRecvParameters 避免触发重启 VideoReceiveStream 逻辑。在 debug 模式下,修改后,验证没有“RecreateWebRtcVideoStream (recv) because of SetRecvParameters”的打印, 即可证明没有 VideoReceiveStream 重启。


音频参数调整

和上面的视频类似,音频也会有因为 rtp extensions 不一致导致重新创建 AudioReceiveStream,也是释放先前的 AudioReceiveStream,再重新创建 AudioReceiveStream。参考代码:

bool WebRtcVoiceMediaChannel::SetRecvParameters(
    const AudioRecvParameters& params) {
  TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::SetRecvParameters");
  RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
  RTC_LOG(LS_INFO) << "WebRtcVoiceMediaChannel::SetRecvParameters: "
                   << params.ToString();
  // TODO(pthatcher): Refactor this to be more clean now that we have
  // all the information at once.
  if (!SetRecvCodecs(params.codecs)) {
    return false;
  }
  if (!ValidateRtpExtensions(params.extensions)) {
    return false;
  }
  std::vector<webrtc::RtpExtension> filtered_extensions = FilterRtpExtensions(
      params.extensions, webrtc::RtpExtension::IsSupportedForAudio, false);
  if (recv_rtp_extensions_ != filtered_extensions) {
    recv_rtp_extensions_.swap(filtered_extensions);
    for (auto& it : recv_streams_) {
      it.second->SetRtpExtensionsAndRecreateStream(recv_rtp_extensions_);
    }
  }
  return true;
}

AudioReceiveStream 的构造方法会启动音频设备,即调用 AudioDeviceModule 的 StartPlayout。AudioReceiveStream 的析构方法会停止音频设备,即调用 AudioDeviceModule 的 StopPlayout。因此重启 AudioReceiveStream 会触发多次 StartPlayout/StopPlayout。经测试,这些不必要的操作会导致进入视频会议的房间时,播放的音频有一小段间断的情况。解决方法同样是通过配置本地支持的 audio codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,避免 AudioReceiveStream 重启逻辑。另外 audio codec 多为 WebRTC 内部实现,去掉一些不用的 Audio Codec,可以减小 WebRTC 对应的库文件。


音视频相互影响

WebRTC 内部有三个非常重要的线程,woker 线程、signal 线程和 network 线程。

调用 PeerConnection 的 API 的调用会由 signal 线程进入到 worker 线程。

worker 线程内完成媒体数据的处理,network 线程处理网络相关的事务,channel.h 文件中有说明,以 _w 结尾的方法为 worker 线程的方法,signal 线程的到 worker 线程的调用是同步操作。如下图中的 InvokerOnWorker 是同步操作,setLocalContent_w 和 setRemoteContent_w 是 worker 线程中的方法。

bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
                                  SdpType type,
                                  std::string* error_desc) {
  TRACE_EVENT0("webrtc", "BaseChannel::SetLocalContent");
  return InvokeOnWorker<bool>(
      RTC_FROM_HERE,
      Bind(&BaseChannel::SetLocalContent_w, this, content, type, error_desc));
}
bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
                                   SdpType type,
                                   std::string* error_desc) {
  TRACE_EVENT0("webrtc", "BaseChannel::SetRemoteContent");
  return InvokeOnWorker<bool>(
      RTC_FROM_HERE,
      Bind(&BaseChannel::SetRemoteContent_w, this, content, type, error_desc));
}

setLocalDescription 和 setRemoteDescription 中的 SDP 信息都会通过 PeerConnection 的 PushdownMediaDescription 方法依次下发给 audio/video RtpTransceiver 设置 SDP 信息。举例,执行 audio 的 SetRemoteContent_w 执行很长(比如音频 AudioDeviceModule 的 InitPlayout 执行耗时), 会影响后面的 video SetRemoteContent_w 的设置时间。PushdownMediaDescription 代码:


RTCError PeerConnection::PushdownMediaDescription(
    SdpType type,
    cricket::ContentSource source) {
  const SessionDescriptionInterface* sdesc =
      (source == cricket::CS_LOCAL ? local_description()
                                   : remote_description());
  RTC_DCHECK(sdesc);
  // Push down the new SDP media section for each audio/video transceiver.
  for (const auto& transceiver : transceivers_) {
    const ContentInfo* content_info =
        FindMediaSectionForTransceiver(transceiver, sdesc);
    cricket::ChannelInterface* channel = transceiver->internal()->channel();
    if (!channel || !content_info || content_info->rejected) {
      continue;
    }
    const MediaContentDescription* content_desc =
        content_info->media_description();
    if (!content_desc) {
      continue;
    }
    std::string error;
    bool success = (source == cricket::CS_LOCAL)
                       ? channel->SetLocalContent(content_desc, type, &error)
                       : channel->SetRemoteContent(content_desc, type, &error);
    if (!success) {
      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, error);
    }
  }
  ...
}

其他影响首帧显示的问题


Android图像宽高16字节对齐


AndroidVideoDecoder 是 WebRTC Android 平台上的视频硬解类。AndroidVideoDecoder 利用 MediaCodec API 完成对硬件解码器的调用。

MediaCodec 有已下解码相关的 API:

  •  dequeueInputBuffer:若大于 0,则是返回填充编码数据的缓冲区的索引,该操作为同步操作。

  • getInputBuffer:填充编码数据的 ByteBuffer 数组,结合 dequeueInputBuffer 返回值,可获取一个可填充编码数据的 ByteBuffer。

  • queueInputBuffer:应用将编码数据拷贝到 ByteBuffer 后,通过该方法告知 MediaCodec 已经填写的编码数据的缓冲区索引。

  • dequeueOutputBuffer:若大于 0,则是返回填充解码数据的缓冲区的索引,该操作为同步操作。

  • getOutputBuffer:填充解码数据的 ByteBuffer 数组,结合 dequeueOutputBuffer 返回值,可获取一个可填充解码数据的 ByteBuffer。

  • releaseOutputBuffer:告诉编码器数据处理完成,释放 ByteBuffer 数据。

在实践当中发现,发送端发送的视频宽高需要 16 字节对齐。因为在某些 Android 手机上解码器需要 16 字节对齐。Android 上视频解码先是把待解码的数据通过 queueInputBuffer 给到 MediaCodec。然后通过 dequeueOutputBuffer 反复查看是否有解完的视频帧。若非 16 字节对齐,dequeueOutputBuffer 会有一次 MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED。而不是一上来就能成功解码一帧。经测试发现,帧宽高非 16 字节对齐会比 16 字节对齐的慢 100 ms 左右。


服务器需转发关键帧请求

iOS 移动设备上,WebRTC App应用进入后台后,视频解码由 VTDecompressionSessionDecodeFrame 返回 kVTInvalidSessionErr,表示解码session 无效。从而会触发观看端的关键帧请求给服务器。这里要求服务器必须转发接收端发来的关键帧请求给发送端。若服务器没有转发关键帧给发送端,接收端就会长时间没有可以渲染的图像,从而出现黑屏问题。这种情况下只能等待发送端自己生成关键帧,发送个接收端,从而使黑屏的接收端恢复正常。


WebRTC内部的一些丢弃数据逻辑举例


Webrtc从接受报数据到、给到解码器之间的过程中也会有很多验证数据的正确性。

举例1

PacketBuffer 中记录着当前缓存的最小的序号 first_seq_num_(这个值也是会被更新的)。当 PacketBuffer 中 InsertPacket 时候,如果即将要插入的 packet 的序号 seq_num 小于 first_seq_num,这个 packet 会被丢弃掉。如果因此持续丢弃 packet,就会有视频不显示或卡顿的情况。

举例2

正常情况下 FrameBuffer 中帧的 picture id,时间戳都是一直正增长的。如果 FrameBuffer 收到 picture_id 比最后解码帧的 picture id 小时,分两种情况:

  • 1. 时间戳比最后解码帧的时间戳大,且是关键帧,就会保存下来;

  • 2. 除情况 1 之外的帧都会丢弃掉;

     

代码如下:

auto last_decoded_frame = decoded_frames_history_.GetLastDecodedFrameId();
auto last_decoded_frame_timestamp =
 decoded_frames_history_.GetLastDecodedFrameTimestamp();
if (last_decoded_frame && id <= *last_decoded_frame) {
if (AheadOf(frame->Timestamp(), *last_decoded_frame_timestamp) &&
   frame->is_keyframe()) {
 // If this frame has a newer timestamp but an earlier picture id then we
 // assume there has been a jump in the picture id due to some encoder
 // reconfiguration or some other reason. Even though this is not according
 // to spec we can still continue to decode from this frame if it is a
 // keyframe.
 RTC_LOG(LS_WARNING)
     << "A jump in picture id was detected, clearing buffer.";
 ClearFramesAndHistory();
 last_continuous_picture_id = -1;
} else {
 RTC_LOG(LS_WARNING) << "Frame with (picture_id:spatial_id) ("
                     << id.picture_id << ":"
                     << static_cast<int>(id.spatial_layer)
                     << ") inserted after frame ("
                     << last_decoded_frame->picture_id << ":"
                     << static_cast<int>(last_decoded_frame->spatial_layer)
                     << ") was handed off for decoding, dropping frame.";
 return last_continuous_picture_id;
}
}

因此为了能让收到了流顺利播放,发送端和中转的服务端需要确保视频帧的 picture_id, 时间戳正确性。

WebRTC 还有其他很多丢帧逻辑,若网络正常且有持续有接收数据,但是视频卡顿或黑屏无显示,多为流本身的问题。


Ending


本文通过分析 WebRTC 音视频接收端的处理逻辑,列举了一些可以优化首帧显示的点,比如通过调整 local SDP 和 remote SDP 中与影响接收端处理的相关部分,从而避免 Audio/Video ReceiveStream 的重启。另外列举了 Android 解码器对视频宽高的要求、服务端对关键帧请求处理、以及 WebRTC 代码内部的一些丢帧逻辑等多个方面对视频显示的影响。这些点都提高了融云 SDK 视频首帧的显示时间,改善了用户体验。


Ruby会消失吗?盘点十年后将要消失的五种编程语言

IM即时通讯大兴 发表了文章 • 0 个评论 • 180 次浏览 • 2020-09-03 14:34 • 来自相关话题

本文作者从自己的观点出发,介绍了未来 20 年内可能消失的 5 个编程语言,并给出了具体的原因。最后对想要学习编程的初学者给出了学习建议。随着时间的流逝,程序员们发现了更新、更简单的工作方式,新的编程语言如雨后春笋般出现,但只有少数编程语言能成为社区的新宠。这... ...查看全部

ÉãͼÍø_501237513_wx_ÌØÁ¢Äá´ïСÕò£¨·ÇÆóÒµÉÌÓã©.jpg


本文作者从自己的观点出发,介绍了未来 20 年内可能消失的 5 个编程语言,并给出了具体的原因。最后对想要学习编程的初学者给出了学习建议。

随着时间的流逝,程序员们发现了更新、更简单的工作方式,新的编程语言如雨后春笋般出现,但只有少数编程语言能成为社区的新宠。这种进步的一个副作用是一些古老的编程语言必然会跟历史一样被人们遗忘。如果一个编程语言无法随着时间的推移提升其价值,那么它的用户群终将会流失,并逐渐淡出人们的视线,或者成为更新一代编程语言的基础。

最近,古老的 COBOL 编程语言上了热搜。在 1960 年代和 1970 年代,它曾经是许多美国银行和政府机构的首选的编程语言,但最终被更加简单有效的编程语言所取代。但是,使用 COBOL 构建的系统仍然存在,当一些政府机构发现他们需要通过更新代码来全面改革失业系统时,才发现业内没有几个开发人员可以熟练使用该编程语言。

沧海桑田,COBOL 早已物是人非。我们当前的许多编程语言也注定会有相似的下场。本文中,我们将分析未来 20 内最终会消失的 5 种编程语言。我知道这可能会伤害到那些正在使用这几个编程语言的程序员的内心,所以在开始介绍之前首先声明下这只是我个人的看法和预测。

1. Ruby

Ruby 在 1999 年发布后立即受到程序员们的热捧,它能够快速构建应用程序的特性给程序员留下了非常深刻的印象。紧随其后,备受欢迎的 Ruby on Rails 框架于 2004 年发布,由于 Ruby 和 Rails 这两个名称在当时几乎成为了同义词,因此 Ruby 很快地被推到了程序员最喜欢的编程语言排行榜首位。它经历了一个坚实的十年,稳居编程语言排行榜前列,一度成为众人瞩目的焦点,但是最近的十年它并不好过,所以 Ruby 纳进了我的淘汰清单列表。

为什么 Ruby 会逐渐消退呢?导致其排名下降的因素之一是其执行速度。由它构建的应用程序运行速度往往比其他流行的编程语言,比如 JavaScript、Go 和 Python 构建的应用(在某些框架下)运行速度慢。同时期下,后三者已经发展到可以满足当时的需求,而 Ruby 在很多方面一直在原地踏步,没有什么进步。例如,让 Ruby on Rails 名噪一时的 MVC 架构在现在被很多编程人员看来是笨重和过时的。

2. Visual Basic

考虑到 Visual Basic 是公认的程序员最不喜欢的编程语言之一的事实,将它包含在淘汰列表中是无可厚非的。它于 1991 年由微软发布,作为构建 Windows 的主要工具,确实实现了此目的,但多年来经常出现问题。程序员使用 VB 进行开发的热情差距很大,随着 2000 年 C#的发布,这种差距逐渐进一步扩大。由于 C#更加简洁,提供了更广泛的功能,并且更适合于云服务和移动开发(在当前市场中扮演着重要角色),因此很多开发人员都放弃使用 VB 转而投向 C#怀抱。最重要的是,似乎它的创建者也都无情地放弃了它,因为微软曾表示他们没有进一步发展它的计划。

3. Haskell

Haskell 是另一个古董级别的编程语言,它于几十年前创建,在世纪之交前一直被使用。不幸的是,对于 Haskell 来说,属于它的时代很快就要结束了。研究人员和学者大多将 Haskell 用于构建程序,以对其工作进行复杂的排列和外推计算,普遍认为它很难学习。学习路径困难必然导致非常有限的活跃用户,而 Haskell 的上一个最新的稳定版本是在 2010 年发布,这对于促进它本身的发展无济于事。

4. Perl

与 Visual Basic 命运一样,Perl 不被大部分使用它的开发人员所喜欢。在 Perl 于 1987 年开始流行时,它被誉为是适合任何一个人的编程语言,无论你是编程的初学者还是专业人士都可以使用,同时还被宣传可以用于各种各样的场景。但是,1991 年 Python 的出现,彻底粉碎了 Perl 主导编程世界的幻想。

尽管 Python 的语法相比 Perl 稍显繁琐,更加严格,但 Python 让用户使用脚本实现目标的过程变得更加简单直接,这改变了当时的游戏规则。Python 在 2000 年代开始让 Perl 黯然失色,并一度成为编程入门的首选语言。在过去的 15 年中,Perl 的使用量一直在稳步下降,我们看不到任何它将停止下滑的迹象。

5. Objective-C

Objective-C 与 Apple 的关系类似于运动相机与 GoPro 的密切联系——两者都是他们所属公司专用的工具。尽管 Objective-C 已经在 macOS、iOS 和 OS X 开发中使用了二十多年(1996 年至今)的时间,但如今已经很少有程序员在使用该语言,因为 Swift 横空出世了。

Swift 由 Apple 构建并于 2014 年发布,旨在成为 macOS、iOS 及其他几个 Apple 相关平台的新编码标准。Obj-C 在很大程度上是基于复杂的 C 语言创建的,而 Swift 有效地删除了这些复杂的元素,同时增加了一些新的功能特性,例如自动内存管理,对 Cocoa Touch 的支持以及类型安全的集成。

Swift 设法实现和维护了较高的语言稳定性和用户采用率,因为 Apple 公司仍在坚持重写 Obj-C 库,以使旧语言的使用者可以很容易地进行过渡。尽管仍然有很多关于这两种选择哪个更好的讨论,但当前的趋势表明,Objective-C 终将被淘汰。

总结

尽管上述提到的语言似乎都处于下降趋势,但不能保证它们最终一定会消失。如果他们的创造者和社区投入足够的精力来更新和维护它们,添加新功能以及有用的特性,这些编程可能会卷土重来,恢复生命力。但事实是广大程序员更有可能继续涌向更新更好的选择。

如果你正在考虑开始学习哪种语言,那么有许多因素需要考虑。如果你只是将编程当做一种兴趣爱好,你可以聆听一下自己内心并选择最能激发你兴趣的那个来学习,即便所选的编程语言不再被广泛使用甚至面临淘汰。另一方面,如果你正在从事编程相关的工作,那么不应该只选择最流行的编程语言,也不应该赌一把选择未来可能会成为最流行的编程语言。而应该通过仔细对比各个编程语言的功能特性,市场需求以及可见的未来增长潜力等因素后做出选择,这才是明智之举。


原文地址https://dzone.com/articles/5-coding-languages-that-will-disappear-in-10-years


作者 | Program Ace

译者 | 王坤祥

厉害了!2020最好的 10 大国外编程学习网站(文末有彩蛋)

IM即时通讯antrue 发表了文章 • 0 个评论 • 193 次浏览 • 2020-08-31 10:16 • 来自相关话题

在这篇文章中,我收集了10个最佳的编程学习网站,掌握编程技能可能是帮助你走出舒适区的一大步,新手程序员通常会觉得程序员市场的竞争太激烈,工作太有挑战性等。但是,据统计,学习编程是值得你花时间去做的事情,原因如下:软件开发人员的平均工资是103,620。软件开发... ...查看全部

在这篇文章中,我收集了10个最佳的编程学习网站,掌握编程技能可能是帮助你走出舒适区的一大步,新手程序员通常会觉得程序员市场的竞争太激烈,工作太有挑战性等。

但是,据统计,学习编程是值得你花时间去做的事情,原因如下:

软件开发人员的平均工资是103,620。

软件开发市场上有很多职位空缺。

简而言之,软件开发是一个快速发展的领域,在这个领域找工作相对不会那么费劲,除此之外,还有大量的学习资源可以学习,以下就是我最喜欢的编程学习网站:

1.Codegym

Codegym是一个以Java为中心的平台,它比其他平台上都要更加深入的介绍Java,这个网站将理论和实践知识相结合,你可以在上面找到1200多个练习项目。主要具备以下特点:

互动课程,这部分有500多小时的编程课程

内置IDE,帮助你解决实践问题

活跃的Java社区

这个平台通过游戏化和故事化让学习变得有趣

地址:https://codegym.cc/ (支持Google账号直接登录)

这个网站还有一大亮点是,提供了在线IDE ,并且可以通过解决 Task 的方式来学习。当你没解决完一个 Task ,你就可以获得相应的奖励。

2. Treehouse

Treehouse是另一个将编程知识简单化讲解的平台,它会教你处理特定项目所需的技能,比如创建一个APP,创建一个WordPress博客等。Treehouse支持所有流行的编程语言——Java、Python、c++、Ruby和其他语言,具有以下好处:

现实环境中练习问题

与导师面对面的交流

离线访问

地址: https://teamtreehouse.com/ (需要另外注册账号)

3. Khan Academy

尽管Khan Academy没有Java或Python教程,但该平台是理解计算机科学基本概念的一个很好的起点,完成这些课程后,你将会了解数据结构和算法相关的知识,并获得“程序员思维”,这个网站具有以下特点:

免费的课程

涉及前端开发和数据库管理

涵盖常见的算法解答

易于使用的界面

有助于巩固知识的小测试

并且,这个网站目前已经支持简体中文版,但是中文版支持的课程比较少。

地址:https://www.khanacademy.org/computing/computer-science (支持Google账号直接登录)

4. Udemy

Udemy是一个领先的在线教育平台,提供大多数编程语言课程,用户群体也相当大,课程分为收费和免费,这是由讲师决定,价格从10美元到几百美元不等,主要具有以下特点:

广泛的编程语言选择

可以按照自己的节奏获取学习材料

地址:https://www.udemy.com/(支持Google账号直接登录)

5. Code4Startup

如果您想在创业领域获得更多实际经验,Code4Startup是一个很好的平台,可以很好地利用你的开发技能。这个项目允许初级程序员为实际的初创公司编写代码,同时,企业主可以节省招聘人才的费用。

除了作为一个实践场所,Code4Startup还为编程专业的学生提供了大量的免费课程。就我个人而言,Learn Ruby on Rails、Heroku和Wistia API等多个课程我都很喜欢。

**地址:**https://code4startup.com/(支持Google、Github账号直接登录)

6. One Month

如果你为“我如何在一个月内学会一门编程语言?”,请务必查看这个平台。这个编程网站可以帮助初级程序员快速掌握JS、Python和Ruby的基本概念,遗憾的是,该平台上的大多数课程都是付费,尽管少数免费课程,还有一个讨论编程热点新闻的博客。

地址:https://onemonth.com/ (需要另外注册账号)

7. PluralSight

Pluralsight的课程从入门到高级,涵盖了所有流行的编程语言,以及数据科学的基础知识,主要具有以下特性:

课程范围广泛

有专门针对高级开发人员的课程

广泛的订阅计划

地址:https://www.pluralsight.com/codeschool (需要另外注册账号)

8. Coursera

与Udemy一样,Coursera是另一个值得CS和编程学生学习的资源。与Udemy不同的是,该平台关注的是大学层面的深入研究,而不是实践理念。

这个平台是计算机科学专业的一个强大的资源,你可以通过它学习斯坦福大学、密歇根大学等学校的课程。

地址:https://www.coursera.org/ (支持facebook和apple账号)

9. Freecodecamp

这是最大的技术社区之一,拥有相当广泛的教程和课程,这在我看来是其他网站都没法比的,你会在这里找到实用的、基于项目的作业——这就是为什么一旦你掌握了扎实的编程理论,最好还是去Freecodecamp看看的原因,与此同时,Freecodecamp上拥有大量的免费资源。

地址:https://www.freecodecamp.org/ (支持Google、Github等账号直接登录)

10. Codewars

Codewars是一个代码练习网站,能够培养程序员,特别是编程新手的逻辑思维能力,如果你经常对编程提不起动力学习,那么,Codewars你值得拥有,砌墙式进阶的方式会让你对编程更加感兴趣。除了刷题进阶之外,它的社区也值得关注,在这里你可以看到许多困扰着程序员的问题,还有别人解答的解决方案

地址:https://www.codewars.com/


11. GeekOnline

1.png

让我们把目光回到国内,由通信云技术领导者融云推出的开发者社区 GeekOnline 正式与全球极客们见面啦!崇尚科技、自由和创造力的极客精神,GeekOnline 致力于成为一个创意与价值兼备、兴趣和温度并存的技术社区。

这是一个开发者们的技术乐园,也是国内首个全面覆盖了 IM、RTC 等通信云技术的极客社区。在移动互联网概念中,设想所有的客户端都“永远在线”,即每条消息发出必有回响。Online 既是一种态度,也是一种精神,正如 IM 所连接的每一个用户,极客永远在线。

地址:https://geekonline.rongcloud.cn/


原文地址:https://levelup.gitconnected.com/its-time-to-start-learning-coding-top-20-best-websites-to-learn-programming-in-2020-9c5105c76c96


编程太无聊?来当导演吧~

好玩创意融云那些事 发表了文章 • 22 个评论 • 3263 次浏览 • 2020-08-06 15:44 • 来自相关话题

对不起~十分抱歉地通知你——当看到此贴的时候,你的“码农”生涯也许就快结束了~曾经~你为之脱发不止的 PHP~JAVA~C,都将离你而去~那个充满名利的娱乐圈,已经敞开了大门从此,你将失去自由,因为每时每刻都有狗仔跟拍~所以~你只能被逼无奈的开着法拉利,带上蛤... ...查看全部

对不起~十分抱歉地通知你——

当看到此贴的时候,你的“码农”生涯也许就快结束了~

曾经~你为之脱发不止的 PHP~JAVA~C,都将离你而去~

那个充满名利的娱乐圈,已经敞开了大门

从此,你将失去自由,因为每时每刻都有狗仔跟拍~

所以~你只能被逼无奈的开着法拉利,带上蛤蟆镜~(也许身边还有一个当演员的女朋友)

因为~你将成为一名~大导演!

 

都说“码农是最具有艺术天赋的群体”~

虽然那一句“hello world”深深地禁锢了你们的肉体,但你们的灵魂永远属于斯坦尼!

我敬爱的“猿”类们——

看着那些自以为是的外行,永远以“格子衫、地中海、单身狗”来固化你们

难道,你的愤怒还要继续隐藏在“//”之后吗?

 

事情是这样的

上半年,融云为展现其“实时音视频”的产品优势,

寻找了多家创意公司甚至影视公司拍摄宣传片~

但他们对开发者真实工作场景的无知,让我们愁眉不展

正应了那句——再牛逼的编剧,也编不出开发者的人生轨迹~

所以,与其让别人“瞎黑”,不如让程序员来“自黑”~

~融云重金邀请不仅会编程,而且还会编剧的“开发文青”

为其全新升级的“实时音视频”编写或执导产品短剧——

 

不是谁都能参加——仅限开发者(不懂编程的,请绕道~)

 

周期还比较紧张——故事大纲选拔截止到20208月31

 

不玩一二三等奖——同时选定10部优秀剧本

 

你最想了解的那些俗事——

1. 选中者将获得5位数或以上的酬劳//别多想~不会超过7位数;

2. 你最想和哪位女演员聊剧本,我们来协调(除奥斯卡影后~);

3. 也许还能参加个电影节,蹭个红地毯,对着镜头说声“hello world”;

 

激动完毕,先看看需求文档吧——

1. 结合开发者真实工作场景,撰写有共鸣度、有笑点(有泪点也OK)的故事;

2. 在故事中,巧妙植入融云“实时音视频”产品优势(植入的越巧妙越好);

3. 单集故事时长控制在3-5分钟;

4. 如果还会画“分镜头剧本”,那就更完美了!

5. 创作者必须保证故事原创性,如出现版权纠纷,由创作者自行解决,融云无连带责任;

 

这次的开发路径是由我们设计的——

1. 开发者先将原创故事的大纲发到本帖的评论区;

2. 融云将对大纲内容进行挑选,选中后将私信与作者沟通详细剧本;

3. 作者将剧本发至指定活动邮箱,融云将安排专业的编剧顾问配合作者对剧本进行优化;

4. 剧本优化后,融云将根据作者(此时应称为“导演”)的要求协调拍摄所需的有关资源;

5. 确定拍摄时间、拍摄地点,并如期拍摄制作;

6. 影片将在B站等视频网站进行首页置顶播放,融云还将协调影片参加相关的评选活动;

 

不知道有木有把事情说清楚,

如果少侠有疑问,可在“评论区”留言,我们将及时解答~

最后,来个“定场诗”收尾——

编程生涯苦无岸,需求文档似高山;

Java&C+亦无趣,秃顶单身格子衫;

日复一日无牵绊,是否也想起波澜?

索性挥毫三千字,当个导演来玩玩!

乐哉!乐哉!

 

(融云“实时音视频”产品功能详见“融云”官网~)


为什么不建议在代码中使用 User 这个单词?

科技创新王叫兽 发表了文章 • 0 个评论 • 76 次浏览 • 2021-05-14 15:24 • 来自相关话题

当你意识到你在项目开始时做的轻量、简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上。现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预期,如果一开始就用正确的方式来做,就不会发生这样的事。今天,我要... ...查看全部

当你意识到你在项目开始时做的轻量、简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上。现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预期,如果一开始就用正确的方式来做,就不会发生这样的事。

今天,我要告诉你的是一个经常犯的错误,一个会给你带来无穷无尽的问题的单词,那就是“users”。

这个单词有两个最基本的错误:

1.对你的需求来说 “User” 几乎从来都不是一个好的描述。2.“User” 会导致一个基本的设计安全缺陷。“user” 的概念是模糊不清的,使用更精准的术语几乎总是能起到更好的效果。

你没有使用者

最开始,没有任何一个软件系统真的有使用者存在。乍一看“user”是一个好的描述,但是你稍微一想就会意识到你的业务逻辑实际上比这要复杂的多。我会使用三个例子,从一个极端的情况出发。机票预订系统没有“users” 我曾经给机票预订系统写过访问控制逻辑,下面只是一小部分需求:

1.旅客可以使用预定记录码通过网站查看预定信息。

2.购买者可以通过信用卡号后四位数在网站上修改预订信息。

3.旅行社可以查看和修改他们的预订。

4.航空公司的值机人员可以根据角色和航空公司来查看和修改预订信息,这需要旅客提供身份信息。

不再一一列举。一些与人类相关的基本概念是“旅客”,“代理”(网站也可是看作代理)和“购买者”。“user”这个概念根本没用,并且在许多请求中我根本不会使用这个单词,举个例子,我们的请求必须包括旅客和代理人的证件,而不是使用者的证件。

Unix 没有 “users”

我们看一个不太一样的例子。Unix (这些天被称为POSIX)有用户,他们可以登录并执行代码。这样看起来很不错吧?我们深入看一下。

如果我们把所有都当作“users”的话,我们将会有:1.使用终端或者图形界面登录的人 2.像邮件或者web服务器这种系统服务也会以“users”的身份运行,例如nginx可以以httpd用户运行。3.在服务器上经常会有多人共享一个管理员账号用来SSH登录(例如,亚马逊的Ubuntu虚拟机默认SSH账号就是‘ubuntu’) 5.root 身份,和上面其他身份都不同。

上面四个是几乎不同的概念,但是在POSIX上他们都是 “users”. 一会儿我们就会看到,把这些概念都称为‘user’会导致很多安全问题。

在操作上,因为POSIX的用户模型边界存在,我们甚至不能找到一种方式说“只能让 Alice 和 Bob 通过这个账号登录”。SaaS 服务提供商没有 “users”

Jeremy Green 最近就用户模型在SaaS中的应用在推特上发文,它第一次提醒了我写下这篇文章,他的基本观点是

SaaS 服务几乎总是:

1.某个组织中的一个人支付服务费用。2.一个或多个人共同使用这个服务。如果你一开始就把这些人作为一个用户,你将会陷入一个痛苦的世界。你无法建立团队模型,你无法组建同时为多人支付的模型,然后你就会开始改造你的系统。现在你在SaaS案例中学到了一课,我们来看一看你的生活。

但是这只是众多例子中的一个:“users”的概念太模糊了。如果你开始怀疑“user”这个词,最终你可能发现最终你其实只需要两个概念:团队(用来组织关系和支付)和成员(实际使用服务的人)。“Users” 是一个安全问题 “user” 这个单词不仅是业务逻辑的问题,它也导致了一系列安全问题。

“user” 这个单词如此的模糊以至于从根本上将两个概念合并了:1.一个人。2.他们在软件中的代表性。

为了说明这个问题,假设你正在访问一个居心不良的网站,在它服务器上的图片导致了你的浏览器内存溢出。远程网站控制着你的浏览器,并且开始将你的文件上传到他的服务上。

为什么它能这样做?

因为浏览器是以系统用户的身份运行的,它被认为与人类身份的你相同,实际上你们是不同的。你作为’user’,不想上传文件。但是系统的账号也是‘user’,能够上传文件,如果浏览器运行在你的账号之下,他所有的行为会被当作是你的意图,也就是说是你让它这么做的,实际上不是。

这就是被称为Confused Deputy的问题。如果你使用“用户”这个词来描述两个根本不同的东西,那么这个问题就更有可能成为你设计的一部分。前期设计的价值

花更少的功夫处理相同的问题是成为高产程序员的关键。使用模糊不清的概念比如“用户”来组织你的软件,将会话费大量时间和精力来解决未来发生的问题。一上来就开始编码看起来是高产的,事实恰好相反。

下次你开始一个新的软件项目时,花几个小时预先确定你的术语和概念:你仍然不会完全正确,但你会做得更好。未来的你将感谢你所做的所有预防浪费的工作。

Geek Online 2020 编程挑战赛作品展示《Script Game》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 209 次浏览 • 2021-04-26 18:14 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:赚钱养家

作品:Script Game

源码地址:https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/46/files

  • Script Game的是一款线上剧本杀APP,它为喜欢剧本和推理的用户提供一个游戏平台。剧本杀是一种注重推理的互动桌面游戏,也是现代青年聚会的常玩娱乐之一。

  • 进入APP的玩家,可以选择不同的剧本,按照游戏流程轮流发言、使用道具,最终完成自己的目的、找到真正的凶手,而凶手则要隐藏自己,嫁祸他人。 在剧本杀中用户将作为一部故事中的角色,可以是侦探推理真凶,可以是古人恩爱情仇,浪荡天涯,可以是现代人心冷暖,在所扮演的角色中,体验到另一种人生。

  • 在Script Game 这个开放包容的APP中,用户可以体验不同的故事,也可以与不同的人们一起扮演不同的角色。

目录

  • APP功能及使用介绍

  • 用到的技术

APP功能及使用介绍

1.剧本杀房间

  • 用户可以选择是否成为一个主持人(组织者)的身份,并且进行剧本杀流程的把控。

  • 每次游戏开始存在人数限制,超出限制的用户可以选择等待或者旁观。

2.剧本选择

  • 不同房间有不同的剧本,用户根据自己想法选择封闭式玩法或者开放式玩法

3.创建剧本

  • 用户可以根据自己需求创建剧本,并且建立房间,成为房间的管理者。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Sing Together》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 210 次浏览 • 2021-04-26 17:46 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:生活不易

作品:Sing Together

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/23/files

  • Sing Together 为各位音乐爱好者提供一个共同K歌的机会。

  • 在这里每个人都可以是“蒙面歌王”,共同体会音乐的魅力,为自己喜欢的歌声打call。

目录

  • APP功能及使用介绍

  • 用到的技术

APP功能及使用介绍

1.K歌室

  • 进入K歌室以后,就可以进行在线K歌功能,在线进行音乐风暴狂欢。用户可选身份有:

    • 主持人

    • 普通K歌

    • 听众

2.粉丝互动

  • Sing Together的平台支持一位主播与多名粉丝的互动活动,粉丝可以为主播点赞、赠送礼物。

3.在线交友

  • 在K歌室中,每个用户之间可以进行互动,通过互动来增加彼此交流。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Warm Heart》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 159 次浏览 • 2021-04-26 17:05 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。


https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:曈塔天线

作品:Warm Heart

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/26/files

  • WarmHeart的开发目标是为人们提供一个情感碰撞平台,人们可以在这里分享自己的喜怒哀乐。在遭受挫折和迷茫时,能够通过WarmHart的暖心室获得他人的温暖和指导。

  • 随着生活节奏的加快,现代人的压力增大,而WarmHeart的主要目标之一是发挥语言的力量,为人们提供心理慰藉。

  • 在WarmHeart可以遇见温暖的普通陌生人,也可以找到专业的情感大师。在每个暖心室里面,都会常驻一位心理咨询师作为主持人,在分享经验的同时还能帮助人们解决心理问题。

  • 本程序为Warm Heart的Andriod版本。

目录

006vbfFply1gjmy81tm7rj30u01pkabg.jpg

APP功能及使用介绍

1.通过手机号注册用户

  • 用户通过手机号,获取验证码即可登录,开启旅程。 

  • 006vbfFply1gjmy8g0h9rj30u01t0js8.jpg

2.暖心畅聊室

  • 这是大家进行交心的地方,由主持人控制房间的情况,每个房间有除主持人外有8个发言位置。

    • 聊天社交

    • 互送礼物

    • 心理咨询

    • 个人分享

    • 更多功能可自行挖掘 

    • 006vbfFply1gjmy91yoz7j30i60msgnk.jpg

3.发布新话题

  • 如果对当前的畅聊室不满意,可以自主新建新的畅聊室。

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

Geek Online 2020 编程挑战赛作品展示《Werewolf》

GeekOnline编程挑战赛admin 发表了文章 • 0 个评论 • 155 次浏览 • 2021-04-26 16:40 • 来自相关话题

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经... ...查看全部

由全球领先的互联网通信云服务商融云主办的 Geek Online 2020 编程挑战赛,于 2020 年 10 月 17 日举办了线上总决赛。本届大赛以“后疫情时代通信云技术的创新及实践”为主题,以融云 IM 和 RTC 的 SDK 为编程项目,历经近两个月的激烈角逐,15 支队伍在近百份参赛作品中突出重围,闯入总决赛,最终决出冠亚季团队及四个单项奖。

https://geekonline.rongcloud.cn/uploads/files_user1/article/607e7d8097e9b481793.png

入围作品主要集中于社交、游戏和工具等多个类别,为鼓励开发者挖掘创意、实现更多实时音视频和即时通讯技术的应用场景,最终,评委根据作品完整性、项目创意性和潜在商业价值三个维度进行综合评分,与数百万在线观众一道,见证了冠军队伍的诞生。

团队:NCC_621

作品:Werewolf

源码地址https://github.com/rongcloud-community/RongCloud_Hackathon_2020/pull/24/files

  • Werewolf的开发目标是为人们提供一个在线狼人杀游戏平台。

  • 在Werewolf中,玩家可以在线开始狼人杀游戏,每个玩家都会被分配到一个身份,每个身份都起到不同的作用。

  • 游戏分为两大阵营:狼人阵营和好人阵营。而胜利条件有多种:所有狼人出局,好人胜利;所有好人出局(屠城)狼人胜利以及所有平民出局或所有神职出局(屠边)狼人胜利。。

  • 在Werewolf中玩家可以自由选择游戏对象,还可以围观他人的游戏过程,体验推理和社交的快乐。

目录

APP功能及使用介绍

1.通过手机号注册用户

-用户通过手机号,获取验证码即可登录,开始游戏。

2.游戏房间

  • 用户进入房间之后,游戏由房间中担任上帝的玩家控制过程,其他成员有各自不同的身份。 --每一局狼人杀游戏都将从夜晚开始,每一个夜晚狼人可以睁眼并共同猎杀场上的一位玩家。夜晚之后将会进入白天,白天是所有玩家发言和投票的环节。 --每个白天场上存活的玩家将按顺序依次进行发言,在所有玩家发言结束后,法官将会组织一次放逐公投,在放逐公投中每一位玩家都拥有一票的投票权,玩家可以选择把票投给自己心目中所认为的狼人。最终,得票数最多的玩家将会被放逐出局并留下“遗言”。 --“遗言”之后游戏将会进入下一个夜晚,狼人又可以再一次睁眼选择一位玩家进行猎杀,如此“黑夜—白天—黑夜”循环,直到某一方阵营达成了胜利条件,则游戏结束。

  • 玩家可以在房间中进行游戏,也可以旁观他人的游戏,也可以创建自己的游戏房间。

 006vbfFpgy1gjmxwh6424j30u01t0jsa.jpg006vbfFpgy1gjmxxaegutj30u01t0taq.jpg

用到的技术

  • 融云 SDK

    • IMKit

    • RCTLib

  • Android JetPack

    • LifeCycles

    • DataBinding

    • LiveData

    • Navigation

    • ViewModel

    • WorkManager

  • Retrofit

  • OkHttp

  • Glide

  • EventBus

  • 本程序实现方案及技术主要自参考融云官网https://www.rongcloud.cn/

如果再给我一次机会,我希望我从未学过编程

好玩创意徐凤年 发表了文章 • 0 个评论 • 122 次浏览 • 2020-11-04 14:39 • 来自相关话题

我是一名程序员。你可能会觉得我是专业的软件工程师,但实际上,软件工程不只是一个专业,还是一种生活方式。连帽衫、乒乓球、吃不完的零食和苏打水……都是这种生活的一部分。但虽然这个职业可以给人带来那么多回报,我还是要坦白一件事:有时我希望自己从来没有学过编程。怪癖自... ...查看全部

我是一名程序员。你可能会觉得我是专业的软件工程师,但实际上,软件工程不只是一个专业,还是一种生活方式。连帽衫、乒乓球、吃不完的零食和苏打水……都是这种生活的一部分。但虽然这个职业可以给人带来那么多回报,我还是要坦白一件事:

有时我希望自己从来没有学过编程。

怪癖

自从我开始学习编程以来,就再也不能像以前那样轻松地浏览网页了。我无法再平静地接受在各种网站上遇到的各种 bug。每当有奇怪的事情发生时,我的好奇心就上了头,然后就会打开浏览器开发工具开始调试网页。

尝试提交表单时出现加密错误消息?看到这样的错误,我会深入 JavaScript 控制台,查找错误消息,深入研究源代码,并仔细观察进进出出的网络请求。页面 UI 看起来有些过时,或者页面布局很漂亮?不管怎样,我都会调整浏览器窗口大小,检查页面响应屏幕尺寸的能力。什么?你竟然选择了 Comic Sans 字体?我可能永远不会再使用你或你们公司的产品了。

微信图片_20201104143349.jpg

请不要使用 ComicSans

职业生涯

觉得我的这些浏览习惯很怪异吗?这还不算啥,我和其他软件工程师的对话会让你印象更深刻的。总体而言,工程师往往固执己见。我们的血液中流淌着迂腐的基因。你喜欢使用空格还是 tabs 缩进代码?你更喜欢 Vim 还是 Emacs?Chrome 还是 Firefox?

聪明点的人会问到,这些对话“真的重要吗?”。Bikeshedding(在琐碎细节上浪费时间)是确实存在的现象,大家都需要经常反省。

微信图片_20201104143403.jpg

xkcd——真正的程序员

随着越来越多的公司采用敏捷方法,我们还得时常考虑在 Waterfall、Scrum 或 Kanban 开发生命周期之间权衡取舍。我们所有人都喜欢鄙视 Waterfall,毕竟这是老式的低效率公司所使用的遗留品。但是 Scrum 与看板之战还在继续。

你竟然是 Scrum 的信徒?我敢打赌,你为了满足那些 time box 会仓促提交混乱的代码,才能在冲刺结束之前赶上假想的时限。

微信图片_20201104143416.jpg

说到敏捷,故事的重点在哪里?时间尺度?努力?复杂性?风险?这些都有?你我可能会花费几小时时间来争论每个定义的优缺点,最后还是无法达成共识。

在某些时候,这种争论可能会演变为更多的 bikeshedding,但能够相互理解和有效沟通是至关重要的,其中就包括了对我们日常使用的术语达成共识。

前端开发带来的争论又是数不清的,其中最典型的是:“我们要到什么时候才会放弃对 Internet Explorer 的支持?”我必须在“讨厌 Internet Explorer,想要放弃它”和了解还在用它的客户需求之间找到完美的平衡点。

微信图片_20201104143428.jpg

持续学习

持续学习是每一位开发者必备的能力,因为技术形势日新月异。每月都有成百上千的新库和框架发布。JavaScript 倦怠不是什么幻觉,“学不动了”也不仅仅是调侃。

我得花一天的时间配置 Webpack、Rollup 和 Babel 以使用最新的 ES6+ 语法,而旁人听我这么说就像在听天书。Angular 和 AngularJS 截然不同?LitElement、Svelte 和 Stencil 似乎是很有前途的 Web 组件解决方案?Deno 可能是下一个 Node?听着都像是谜语。

当有人问我做什么工作时,我总会蹦出来一堆术语:“我通常使用前端技术,例如 HTML、CSS 和 JS。有时我必须使用 PHP 或 SQL,但我更多是 MEAN/MERN 栈开发人员。有时我使用 Heroku 之类的 PaaS 技术,还有时我会使用 AWS 或 GCP 之类的 IaaS 提供商。”

微信图片_20201104143437.jpg

个人生活

在业余时间里,我读的书干货十足,例如《干净代码》《重构》和《领域驱动设计》。我不是在看教科书,就是在阅读里面写着可疑建议的文章,或观看一些编程教程。关于编程的播客更能吸引人,这样我就能在路上听某人谈论写代码的方法,然后我又可以花一天时间谈相关话题了。

除了在线获取内容外,软件工程师还会花费大量个人时间来在网上创建内容。我个人的兴趣是构建简单的应用和游戏,其实没人看得到。这是浪费时间吗?也许是吧,但的确挺有意思的。


微信图片_20201104143447.jpg企业 BS 生成器应用

事实是

可事实是,编程给了我创造和创新的机会。它帮助我将创意变为现实,让我几乎从零开始构筑了很多东西。软件工程使我能够解决有趣而艰巨的挑战,理想情况下还能让人们的生活更轻松一些。编程使我的思维更具逻辑性。编程让我有机会不断学习,我还能以编程为职业获取薪水!

事实是,我喜欢编程。

参考阅读

https://hackernoon.com/i-wish-i-never-learned-to-code-7a1m3wwx

手把手教学|用代码写一个单机五子棋

IM即时通讯大神庵 发表了文章 • 0 个评论 • 195 次浏览 • 2020-10-14 16:26 • 来自相关话题

这篇文章主要为大家详细介绍了python实现单机五子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。# 简介这是实验室2018年底招新时的考核题目,使用Python编写一个能够完成基本对战的五子棋游戏。面向新手。程序主要包括两... ...查看全部

这篇文章主要为大家详细介绍了python实现单机五子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。

# 简介

这是实验室2018年底招新时的考核题目,使用Python编写一个能够完成基本对战的五子棋游戏。面向新手。

程序主要包括两个部分,图形创建与逻辑编写两部分。

程序的运行结果:

640.png

# 样式创建

老规矩,先把用到的包导入进来。

'''
@Auther : gaoxin
@Date : 2019.01.01
@Version : 1.0
'''

from tkinter import *
import math
12345678

然后建立一个样式的类,类名称chessBoard。这里加了很多注释,避免新手看不懂函数的作用,说实话我觉得挺别扭的。

#定义棋盘类
class chessBoard() :
    def __init__(self) :
     #创建一个tk对象,即窗口
        self.window = Tk()
        #窗口命名
        self.window.title("五子棋游戏")
        #定义窗口大小
        self.window.geometry("660x470")
        #定义窗口不可放缩
        self.window.resizable(0,0)
        #定义窗口里的画布
        self.canvas=Canvas(self.window , bg="#EEE8AC" , width=470, height=470)
        #画出画布内容
        self.paint_board()
        #定义画布所在的网格
        self.canvas.grid(row = 0 , column = 0)
    def paint_board(self) :
     #画横线
        for row in range(0,15) :
            if row == 0 or row == 14 :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 2)
            else :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 1)
        #画竖线
        for column in range(0,15) :
            if column == 0 or column == 14 :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 ,width = 2)
            else :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 , width = 1)
        #画圆
        self.canvas.create_oval(112, 112, 118, 118, fill="black")
        self.canvas.create_oval(352, 112, 358, 118, fill="black")
        self.canvas.create_oval(112, 352, 118, 358, fill="black")
        self.canvas.create_oval(232, 232, 238, 238, fill="black")
        self.canvas.create_oval(352, 352, 358, 358, fill="black")
1234567891011121314151617181920212223242526272829303132333435363738394041

# 逻辑编写


这里的主要看每个函数的功能就好了。

#定义五子棋游戏类
#0为黑子 , 1为白子 , 2为空位
class Gobang() :
    #初始化
    def __init__(self) :
        self.board = chessBoard()
        self.game_print = StringVar()
        self.game_print.set("")
        #16*16的二维列表,保证不会out of index
        self.db = [([2] * 16) for i in range(16)]
        #悔棋用的顺序列表
        self.order = []
        #棋子颜色
        self.color_count = 0
        self.color = 'black'
        #清空与赢的初始化,已赢为1,已清空为1
        self.flag_win = 1
        self.flag_empty = 1
        self.options()
    #黑白互换
    def change_color(self) :
        self.color_count = (self.color_count + 1 ) % 2
        if self.color_count == 0 :
            self.color = "black"
        elif self.color_count ==1 :
            self.color = "white"
    #落子
    def chess_moving(self ,event) :
        #不点击“开始”与“清空”无法再次开始落子
        if self.flag_win ==1 or self.flag_empty ==0  :
            return
        #坐标转化为下标
        x,y = event.x-25 , event.y-25
        x = round(x/30)
        y = round(y/30)
        #点击位置没用落子,且没有在棋盘线外,可以落子
        while self.db[y][x] == 2 and self.limit_boarder(y,x):
            self.db[y][x] = self.color_count
            self.order.append(x+15*y)
            self.board.canvas.create_oval(25+30*x-12 , 25+30*y-12 , 25+30*x+12 , 25+30*y+12 , fill = self.color,tags = "chessman")
            if self.game_win(y,x,self.color_count) :
                print(self.color,"获胜")
                self.game_print.set(self.color+"获胜")
            else :
                self.change_color()
                self.game_print.set("请"+self.color+"落子")
    #保证棋子落在棋盘上
    def limit_boarder(self , y , x) :
        if x<0 or x>14 or y<0 or y>14 :
            return False
        else :
            return True
    #计算连子的数目,并返回最大连子数目
    def chessman_count(self , y , x , color_count ) :
        count1,count2,count3,count4 = 1,1,1,1
        #横计算
        for i in range(-1 , -5 , -1) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        #竖计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        #/计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        #\计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        return max(count1 , count2 , count3 , count4)
    #判断输赢
    def game_win(self , y , x , color_count ) :
        if self.chessman_count(y,x,color_count) >= 5 :
            self.flag_win = 1
            self.flag_empty = 0
            return True
        else :
            return False
    #悔棋,清空棋盘,再画剩下的n-1个棋子
    def withdraw(self ) :
        if len(self.order)==0 or self.flag_win == 1:
            return
        self.board.canvas.delete("chessman")
        z = self.order.pop()
        x = z
        y = z//15
        self.db[y][x] = 2
        self.color_count = 1
        for i in self.order :
            ix = i
            iy = i//15
            self.change_color()
            self.board.canvas.create_oval(25+30*ix-12 , 25+30*iy-12 , 25+30*ix+12 , 25+30*iy+12 , fill = self.color,tags = "chessman")
        self.change_color()
        self.game_print.set("请"+self.color+"落子")
    #清空
    def empty_all(self) :
        self.board.canvas.delete("chessman")
        #还原初始化
        self.db = [([2] * 16) for i in range(16)]
        self.order = []
        self.color_count = 0
        self.color = 'black'
        self.flag_win = 1
        self.flag_empty = 1
        self.game_print.set("")
    #将self.flag_win置0才能在棋盘上落子
    def game_start(self) :
        #没有清空棋子不能置0开始
        if self.flag_empty == 0:
            return
        self.flag_win = 0
        self.game_print.set("请"+self.color+"落子")
    def options(self) :
        self.board.canvas.bind("<Button-1>",self.chess_moving)
        Label(self.board.window , textvariable = self.game_print , font = ("Arial", 20) ).place(relx = 0, rely = 0 ,x = 495 , y = 200)
        Button(self.board.window , text= "开始游戏" ,command = self.game_start,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=15)
        Button(self.board.window , text= "我要悔棋" ,command = self.withdraw,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=60)
        Button(self.board.window , text= "清空棋局" ,command = self.empty_all,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=105)
        Button(self.board.window , text= "结束游戏" ,command = self.board.window.destroy,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=420)
        self.board.window.mainloop()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171

最后,main函数

if __name__ == "__main__":
    game = Gobang()
1234

将以上的所有程序复制粘贴,即为完整的程序了,可以运行。


最后来一个完整程序,一个一个复制粘贴简直不要太麻烦。

'''
@Auther : gaoxin
@Date : 2019.01.01
@Version : 1.0
'''
from tkinter import *
import math
#定义棋盘类
class chessBoard() :
    def __init__(self) :
        self.window = Tk()
        self.window.title("五子棋游戏")
        self.window.geometry("660x470")
        self.window.resizable(0,0)
        self.canvas=Canvas(self.window , bg="#EEE8AC" , width=470, height=470)
        self.paint_board()
        self.canvas.grid(row = 0 , column = 0)
    def paint_board(self) :
        for row in range(0,15) :
            if row == 0 or row == 14 :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 2)
            else :
                self.canvas.create_line(25 , 25+row*30 , 25+14*30 , 25+row*30 , width = 1)
        for column in range(0,15) :
            if column == 0 or column == 14 :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 ,width = 2)
            else :
                self.canvas.create_line(25+column*30 ,25, 25+column*30 , 25+14*30 , width = 1)
        self.canvas.create_oval(112, 112, 118, 118, fill="black")
        self.canvas.create_oval(352, 112, 358, 118, fill="black")
        self.canvas.create_oval(112, 352, 118, 358, fill="black")
        self.canvas.create_oval(232, 232, 238, 238, fill="black")
        self.canvas.create_oval(352, 352, 358, 358, fill="black")
#定义五子棋游戏类
#0为黑子 , 1为白子 , 2为空位
class Gobang() :
    #初始化
    def __init__(self) :
        self.board = chessBoard()
        self.game_print = StringVar()
        self.game_print.set("")
        #16*16的二维列表,保证不会out of index
        self.db = [([2] * 16) for i in range(16)]
        #悔棋用的顺序列表
        self.order = []
        #棋子颜色
        self.color_count = 0
        self.color = 'black'
        #清空与赢的初始化,已赢为1,已清空为1
        self.flag_win = 1
        self.flag_empty = 1
        self.options()
    #黑白互换
    def change_color(self) :
        self.color_count = (self.color_count + 1 ) % 2
        if self.color_count == 0 :
            self.color = "black"
        elif self.color_count ==1 :
            self.color = "white"
    #落子
    def chess_moving(self ,event) :
        #不点击“开始”与“清空”无法再次开始落子
        if self.flag_win ==1 or self.flag_empty ==0  :
            return
        #坐标转化为下标
        x,y = event.x-25 , event.y-25
        x = round(x/30)
        y = round(y/30)
        #点击位置没用落子,且没有在棋盘线外,可以落子
        while self.db[y][x] == 2 and self.limit_boarder(y,x):
            self.db[y][x] = self.color_count
            self.order.append(x+15*y)
            self.board.canvas.create_oval(25+30*x-12 , 25+30*y-12 , 25+30*x+12 , 25+30*y+12 , fill = self.color,tags = "chessman")
            if self.game_win(y,x,self.color_count) :
                print(self.color,"获胜")
                self.game_print.set(self.color+"获胜")
            else :
                self.change_color()
                self.game_print.set("请"+self.color+"落子")
    #保证棋子落在棋盘上
    def limit_boarder(self , y , x) :
        if x<0 or x>14 or y<0 or y>14 :
            return False
        else :
            return True
    #计算连子的数目,并返回最大连子数目
    def chessman_count(self , y , x , color_count ) :
        count1,count2,count3,count4 = 1,1,1,1
        #横计算
        for i in range(-1 , -5 , -1) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y][x+i] == color_count  :
                count1 += 1
            else:
                break
        #竖计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x] == color_count  :
                count2 += 1
            else:
                break
        #/计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x+i] == color_count  :
                count3 += 1
            else:
                break
        #\计算
        for i in range(-1 , -5 , -1) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        for i in  range(1 , 5 ,1 ) :
            if self.db[y+i][x-i] == color_count :
                count4 += 1
            else:
                break
        return max(count1 , count2 , count3 , count4)
    #判断输赢
    def game_win(self , y , x , color_count ) :
        if self.chessman_count(y,x,color_count) >= 5 :
            self.flag_win = 1
            self.flag_empty = 0
            return True
        else :
            return False
    #悔棋,清空棋盘,再画剩下的n-1个棋子
    def withdraw(self ) :
        if len(self.order)==0 or self.flag_win == 1:
            return
        self.board.canvas.delete("chessman")
        z = self.order.pop()
        x = z
        y = z//15
        self.db[y][x] = 2
        self.color_count = 1
        for i in self.order :
            ix = i
            iy = i//15
            self.change_color()
            self.board.canvas.create_oval(25+30*ix-12 , 25+30*iy-12 , 25+30*ix+12 , 25+30*iy+12 , fill = self.color,tags = "chessman")
        self.change_color()
        self.game_print.set("请"+self.color+"落子")
    #清空
    def empty_all(self) :
        self.board.canvas.delete("chessman")
        #还原初始化
        self.db = [([2] * 16) for i in range(16)]
        self.order = []
        self.color_count = 0
        self.color = 'black'
        self.flag_win = 1
        self.flag_empty = 1
        self.game_print.set("")
    #将self.flag_win置0才能在棋盘上落子
    def game_start(self) :
        #没有清空棋子不能置0开始
        if self.flag_empty == 0:
            return
        self.flag_win = 0
        self.game_print.set("请"+self.color+"落子")
    def options(self) :
        self.board.canvas.bind("<Button-1>",self.chess_moving)
        Label(self.board.window , textvariable = self.game_print , font = ("Arial", 20) ).place(relx = 0, rely = 0 ,x = 495 , y = 200)
        Button(self.board.window , text= "开始游戏" ,command = self.game_start,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=15)
        Button(self.board.window , text= "我要悔棋" ,command = self.withdraw,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=60)
        Button(self.board.window , text= "清空棋局" ,command = self.empty_all,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=105)
        Button(self.board.window , text= "结束游戏" ,command = self.board.window.destroy,width = 13, font = ("Verdana", 12)).place(relx=0, rely=0, x=495, y=420)
        self.board.window.mainloop()
if __name__ == "__main__":
    game = Gobang()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

来源:https://blog.csdn.net/gaosanjin/article/details/108244164


融云 WebRTC 首帧显示优化策略到底有多强?

WebRTCadmin 发表了文章 • 0 个评论 • 173 次浏览 • 2020-09-29 17:47 • 来自相关话题

作者:融云 WebRTC 高级工程师 苏道音视频实时通话首帧的显示是一项重要的用户体验标准。本文主要通过对接收端的分析来了解和优化视频首帧的显示时间。流程介绍发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端... ...查看全部

timg.jpg

作者:融云 WebRTC 高级工程师 苏道


音视频实时通话首帧的显示是一项重要的用户体验标准。本文主要通过对接收端的分析来了解和优化视频首帧的显示时间。


流程介绍

发送端采集音视频数据,通过编码器生成帧数据。这数据被打包成 RTP 包,通过 ICE 通道发送到接收端。接收端接收 RTP 包,取出 RTP payload,完成组帧的操作。之后音视频解码器解码帧数据,生成视频图像或音频 PCM 数据。

640.png


本文参数调整谈论的部分位于上图中的第 4 步。因为是接收端,所以会收到对方的 Offer 请求。先设置 SetRemoteDescription 再 SetLocalDescription。如下图蓝色部分:

2.png


参数调整

视频参数调整

当收到 Signal 线程 SetRemoteDescription 后,会在 Worker 线程中创建 VideoReceiveStream 对象。具体流程为 SetRemoteDescription

VideoChannel::SetRemoteContent_w 创建 WebRtcVideoReceiveStream。

WebRtcVideoReceiveStream 包含了一个 VideoReceiveStream 类型 stream_ 对象,通过 webrtc::VideoReceiveStream* Call::CreateVideoReceiveStream 创建。创建后立即启动 VideoReceiveStream 工作,即调用 Start() 方法。此时 VideoReceiveStream 包含一个 RtpVideoStreamReceiver 对象准备开始处理 video RTP 包。接收方创建 createAnswer 后通过 setLocalDescription 设置 local descritpion。对应会在 Worker 线程中 setLocalContent_w 方法中根据 SDP 设置 channel 的接收参数,最终会调用到 WebRtcVideoReceiveStream::SetRecvParameters。


WebRtcVideoReceiveStream::SetRecvParameters 实现如下:

void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetRecvParameters(
    const ChangedRecvParameters& params) {
  bool video_needs_recreation = false;
  bool flexfec_needs_recreation = false;
  if (params.codec_settings) {
    ConfigureCodecs(*params.codec_settings);
    video_needs_recreation = true;
  }
  if (params.rtp_header_extensions) {
    config_.rtp.extensions = *params.rtp_header_extensions;
    flexfec_config_.rtp_header_extensions = *params.rtp_header_extensions;
    video_needs_recreation = true;
    flexfec_needs_recreation = true;
  }
  if (params.flexfec_payload_type) {
    ConfigureFlexfecCodec(*params.flexfec_payload_type);
    flexfec_needs_recreation = true;
  }
  if (flexfec_needs_recreation) {
    RTC_LOG(LS_INFO) << "MaybeRecreateWebRtcFlexfecStream (recv) because of "
                        "SetRecvParameters";
    MaybeRecreateWebRtcFlexfecStream();
  }
  if (video_needs_recreation) {
    RTC_LOG(LS_INFO)
        << "RecreateWebRtcVideoStream (recv) because of SetRecvParameters";
    RecreateWebRtcVideoStream();
  }
}

根据上图中 SetRecvParameters 代码,如果 codec_settings 不为空、rtp_header_extensions 不为空、flexfec_payload_type 不为空都会重启 VideoReceiveStream。video_needs_recreation 表示是否要重启 VideoReceiveStream。重启过程为,把先前创建的释放掉,然后重建新的 VideoReceiveStream。以 codec_settings 为例,初始 video codec 支持 H264 和 VP8。若对端只支持 H264,协商后的 codec 仅支持 H264。SetRecvParameters 中的 codec_settings 为 H264 不空。其实前后 VideoReceiveStream 的都有 H264 codec,没有必要重建 VideoReceiveStream。可以通过配置本地支持的 video codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,并且判断 codec_settings 是否相等。如果不相等再 video_needs_recreation 为 true。这样设置就会使 SetRecvParameters 避免触发重启 VideoReceiveStream 逻辑。在 debug 模式下,修改后,验证没有“RecreateWebRtcVideoStream (recv) because of SetRecvParameters”的打印, 即可证明没有 VideoReceiveStream 重启。


音频参数调整

和上面的视频类似,音频也会有因为 rtp extensions 不一致导致重新创建 AudioReceiveStream,也是释放先前的 AudioReceiveStream,再重新创建 AudioReceiveStream。参考代码:

bool WebRtcVoiceMediaChannel::SetRecvParameters(
    const AudioRecvParameters& params) {
  TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::SetRecvParameters");
  RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
  RTC_LOG(LS_INFO) << "WebRtcVoiceMediaChannel::SetRecvParameters: "
                   << params.ToString();
  // TODO(pthatcher): Refactor this to be more clean now that we have
  // all the information at once.
  if (!SetRecvCodecs(params.codecs)) {
    return false;
  }
  if (!ValidateRtpExtensions(params.extensions)) {
    return false;
  }
  std::vector<webrtc::RtpExtension> filtered_extensions = FilterRtpExtensions(
      params.extensions, webrtc::RtpExtension::IsSupportedForAudio, false);
  if (recv_rtp_extensions_ != filtered_extensions) {
    recv_rtp_extensions_.swap(filtered_extensions);
    for (auto& it : recv_streams_) {
      it.second->SetRtpExtensionsAndRecreateStream(recv_rtp_extensions_);
    }
  }
  return true;
}

AudioReceiveStream 的构造方法会启动音频设备,即调用 AudioDeviceModule 的 StartPlayout。AudioReceiveStream 的析构方法会停止音频设备,即调用 AudioDeviceModule 的 StopPlayout。因此重启 AudioReceiveStream 会触发多次 StartPlayout/StopPlayout。经测试,这些不必要的操作会导致进入视频会议的房间时,播放的音频有一小段间断的情况。解决方法同样是通过配置本地支持的 audio codec 初始列表和 rtp extensions,从而生成的 local SDP 和 remote SDP 中影响接收参数部分调整一致,避免 AudioReceiveStream 重启逻辑。另外 audio codec 多为 WebRTC 内部实现,去掉一些不用的 Audio Codec,可以减小 WebRTC 对应的库文件。


音视频相互影响

WebRTC 内部有三个非常重要的线程,woker 线程、signal 线程和 network 线程。

调用 PeerConnection 的 API 的调用会由 signal 线程进入到 worker 线程。

worker 线程内完成媒体数据的处理,network 线程处理网络相关的事务,channel.h 文件中有说明,以 _w 结尾的方法为 worker 线程的方法,signal 线程的到 worker 线程的调用是同步操作。如下图中的 InvokerOnWorker 是同步操作,setLocalContent_w 和 setRemoteContent_w 是 worker 线程中的方法。

bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
                                  SdpType type,
                                  std::string* error_desc) {
  TRACE_EVENT0("webrtc", "BaseChannel::SetLocalContent");
  return InvokeOnWorker<bool>(
      RTC_FROM_HERE,
      Bind(&BaseChannel::SetLocalContent_w, this, content, type, error_desc));
}
bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
                                   SdpType type,
                                   std::string* error_desc) {
  TRACE_EVENT0("webrtc", "BaseChannel::SetRemoteContent");
  return InvokeOnWorker<bool>(
      RTC_FROM_HERE,
      Bind(&BaseChannel::SetRemoteContent_w, this, content, type, error_desc));
}

setLocalDescription 和 setRemoteDescription 中的 SDP 信息都会通过 PeerConnection 的 PushdownMediaDescription 方法依次下发给 audio/video RtpTransceiver 设置 SDP 信息。举例,执行 audio 的 SetRemoteContent_w 执行很长(比如音频 AudioDeviceModule 的 InitPlayout 执行耗时), 会影响后面的 video SetRemoteContent_w 的设置时间。PushdownMediaDescription 代码:


RTCError PeerConnection::PushdownMediaDescription(
    SdpType type,
    cricket::ContentSource source) {
  const SessionDescriptionInterface* sdesc =
      (source == cricket::CS_LOCAL ? local_description()
                                   : remote_description());
  RTC_DCHECK(sdesc);
  // Push down the new SDP media section for each audio/video transceiver.
  for (const auto& transceiver : transceivers_) {
    const ContentInfo* content_info =
        FindMediaSectionForTransceiver(transceiver, sdesc);
    cricket::ChannelInterface* channel = transceiver->internal()->channel();
    if (!channel || !content_info || content_info->rejected) {
      continue;
    }
    const MediaContentDescription* content_desc =
        content_info->media_description();
    if (!content_desc) {
      continue;
    }
    std::string error;
    bool success = (source == cricket::CS_LOCAL)
                       ? channel->SetLocalContent(content_desc, type, &error)
                       : channel->SetRemoteContent(content_desc, type, &error);
    if (!success) {
      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, error);
    }
  }
  ...
}

其他影响首帧显示的问题


Android图像宽高16字节对齐


AndroidVideoDecoder 是 WebRTC Android 平台上的视频硬解类。AndroidVideoDecoder 利用 MediaCodec API 完成对硬件解码器的调用。

MediaCodec 有已下解码相关的 API:

  •  dequeueInputBuffer:若大于 0,则是返回填充编码数据的缓冲区的索引,该操作为同步操作。

  • getInputBuffer:填充编码数据的 ByteBuffer 数组,结合 dequeueInputBuffer 返回值,可获取一个可填充编码数据的 ByteBuffer。

  • queueInputBuffer:应用将编码数据拷贝到 ByteBuffer 后,通过该方法告知 MediaCodec 已经填写的编码数据的缓冲区索引。

  • dequeueOutputBuffer:若大于 0,则是返回填充解码数据的缓冲区的索引,该操作为同步操作。

  • getOutputBuffer:填充解码数据的 ByteBuffer 数组,结合 dequeueOutputBuffer 返回值,可获取一个可填充解码数据的 ByteBuffer。

  • releaseOutputBuffer:告诉编码器数据处理完成,释放 ByteBuffer 数据。

在实践当中发现,发送端发送的视频宽高需要 16 字节对齐。因为在某些 Android 手机上解码器需要 16 字节对齐。Android 上视频解码先是把待解码的数据通过 queueInputBuffer 给到 MediaCodec。然后通过 dequeueOutputBuffer 反复查看是否有解完的视频帧。若非 16 字节对齐,dequeueOutputBuffer 会有一次 MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED。而不是一上来就能成功解码一帧。经测试发现,帧宽高非 16 字节对齐会比 16 字节对齐的慢 100 ms 左右。


服务器需转发关键帧请求

iOS 移动设备上,WebRTC App应用进入后台后,视频解码由 VTDecompressionSessionDecodeFrame 返回 kVTInvalidSessionErr,表示解码session 无效。从而会触发观看端的关键帧请求给服务器。这里要求服务器必须转发接收端发来的关键帧请求给发送端。若服务器没有转发关键帧给发送端,接收端就会长时间没有可以渲染的图像,从而出现黑屏问题。这种情况下只能等待发送端自己生成关键帧,发送个接收端,从而使黑屏的接收端恢复正常。


WebRTC内部的一些丢弃数据逻辑举例


Webrtc从接受报数据到、给到解码器之间的过程中也会有很多验证数据的正确性。

举例1

PacketBuffer 中记录着当前缓存的最小的序号 first_seq_num_(这个值也是会被更新的)。当 PacketBuffer 中 InsertPacket 时候,如果即将要插入的 packet 的序号 seq_num 小于 first_seq_num,这个 packet 会被丢弃掉。如果因此持续丢弃 packet,就会有视频不显示或卡顿的情况。

举例2

正常情况下 FrameBuffer 中帧的 picture id,时间戳都是一直正增长的。如果 FrameBuffer 收到 picture_id 比最后解码帧的 picture id 小时,分两种情况:

  • 1. 时间戳比最后解码帧的时间戳大,且是关键帧,就会保存下来;

  • 2. 除情况 1 之外的帧都会丢弃掉;

     

代码如下:

auto last_decoded_frame = decoded_frames_history_.GetLastDecodedFrameId();
auto last_decoded_frame_timestamp =
 decoded_frames_history_.GetLastDecodedFrameTimestamp();
if (last_decoded_frame && id <= *last_decoded_frame) {
if (AheadOf(frame->Timestamp(), *last_decoded_frame_timestamp) &&
   frame->is_keyframe()) {
 // If this frame has a newer timestamp but an earlier picture id then we
 // assume there has been a jump in the picture id due to some encoder
 // reconfiguration or some other reason. Even though this is not according
 // to spec we can still continue to decode from this frame if it is a
 // keyframe.
 RTC_LOG(LS_WARNING)
     << "A jump in picture id was detected, clearing buffer.";
 ClearFramesAndHistory();
 last_continuous_picture_id = -1;
} else {
 RTC_LOG(LS_WARNING) << "Frame with (picture_id:spatial_id) ("
                     << id.picture_id << ":"
                     << static_cast<int>(id.spatial_layer)
                     << ") inserted after frame ("
                     << last_decoded_frame->picture_id << ":"
                     << static_cast<int>(last_decoded_frame->spatial_layer)
                     << ") was handed off for decoding, dropping frame.";
 return last_continuous_picture_id;
}
}

因此为了能让收到了流顺利播放,发送端和中转的服务端需要确保视频帧的 picture_id, 时间戳正确性。

WebRTC 还有其他很多丢帧逻辑,若网络正常且有持续有接收数据,但是视频卡顿或黑屏无显示,多为流本身的问题。


Ending


本文通过分析 WebRTC 音视频接收端的处理逻辑,列举了一些可以优化首帧显示的点,比如通过调整 local SDP 和 remote SDP 中与影响接收端处理的相关部分,从而避免 Audio/Video ReceiveStream 的重启。另外列举了 Android 解码器对视频宽高的要求、服务端对关键帧请求处理、以及 WebRTC 代码内部的一些丢帧逻辑等多个方面对视频显示的影响。这些点都提高了融云 SDK 视频首帧的显示时间,改善了用户体验。


Ruby会消失吗?盘点十年后将要消失的五种编程语言

IM即时通讯大兴 发表了文章 • 0 个评论 • 180 次浏览 • 2020-09-03 14:34 • 来自相关话题

本文作者从自己的观点出发,介绍了未来 20 年内可能消失的 5 个编程语言,并给出了具体的原因。最后对想要学习编程的初学者给出了学习建议。随着时间的流逝,程序员们发现了更新、更简单的工作方式,新的编程语言如雨后春笋般出现,但只有少数编程语言能成为社区的新宠。这... ...查看全部

ÉãͼÍø_501237513_wx_ÌØÁ¢Äá´ïСÕò£¨·ÇÆóÒµÉÌÓã©.jpg


本文作者从自己的观点出发,介绍了未来 20 年内可能消失的 5 个编程语言,并给出了具体的原因。最后对想要学习编程的初学者给出了学习建议。

随着时间的流逝,程序员们发现了更新、更简单的工作方式,新的编程语言如雨后春笋般出现,但只有少数编程语言能成为社区的新宠。这种进步的一个副作用是一些古老的编程语言必然会跟历史一样被人们遗忘。如果一个编程语言无法随着时间的推移提升其价值,那么它的用户群终将会流失,并逐渐淡出人们的视线,或者成为更新一代编程语言的基础。

最近,古老的 COBOL 编程语言上了热搜。在 1960 年代和 1970 年代,它曾经是许多美国银行和政府机构的首选的编程语言,但最终被更加简单有效的编程语言所取代。但是,使用 COBOL 构建的系统仍然存在,当一些政府机构发现他们需要通过更新代码来全面改革失业系统时,才发现业内没有几个开发人员可以熟练使用该编程语言。

沧海桑田,COBOL 早已物是人非。我们当前的许多编程语言也注定会有相似的下场。本文中,我们将分析未来 20 内最终会消失的 5 种编程语言。我知道这可能会伤害到那些正在使用这几个编程语言的程序员的内心,所以在开始介绍之前首先声明下这只是我个人的看法和预测。

1. Ruby

Ruby 在 1999 年发布后立即受到程序员们的热捧,它能够快速构建应用程序的特性给程序员留下了非常深刻的印象。紧随其后,备受欢迎的 Ruby on Rails 框架于 2004 年发布,由于 Ruby 和 Rails 这两个名称在当时几乎成为了同义词,因此 Ruby 很快地被推到了程序员最喜欢的编程语言排行榜首位。它经历了一个坚实的十年,稳居编程语言排行榜前列,一度成为众人瞩目的焦点,但是最近的十年它并不好过,所以 Ruby 纳进了我的淘汰清单列表。

为什么 Ruby 会逐渐消退呢?导致其排名下降的因素之一是其执行速度。由它构建的应用程序运行速度往往比其他流行的编程语言,比如 JavaScript、Go 和 Python 构建的应用(在某些框架下)运行速度慢。同时期下,后三者已经发展到可以满足当时的需求,而 Ruby 在很多方面一直在原地踏步,没有什么进步。例如,让 Ruby on Rails 名噪一时的 MVC 架构在现在被很多编程人员看来是笨重和过时的。

2. Visual Basic

考虑到 Visual Basic 是公认的程序员最不喜欢的编程语言之一的事实,将它包含在淘汰列表中是无可厚非的。它于 1991 年由微软发布,作为构建 Windows 的主要工具,确实实现了此目的,但多年来经常出现问题。程序员使用 VB 进行开发的热情差距很大,随着 2000 年 C#的发布,这种差距逐渐进一步扩大。由于 C#更加简洁,提供了更广泛的功能,并且更适合于云服务和移动开发(在当前市场中扮演着重要角色),因此很多开发人员都放弃使用 VB 转而投向 C#怀抱。最重要的是,似乎它的创建者也都无情地放弃了它,因为微软曾表示他们没有进一步发展它的计划。

3. Haskell

Haskell 是另一个古董级别的编程语言,它于几十年前创建,在世纪之交前一直被使用。不幸的是,对于 Haskell 来说,属于它的时代很快就要结束了。研究人员和学者大多将 Haskell 用于构建程序,以对其工作进行复杂的排列和外推计算,普遍认为它很难学习。学习路径困难必然导致非常有限的活跃用户,而 Haskell 的上一个最新的稳定版本是在 2010 年发布,这对于促进它本身的发展无济于事。

4. Perl

与 Visual Basic 命运一样,Perl 不被大部分使用它的开发人员所喜欢。在 Perl 于 1987 年开始流行时,它被誉为是适合任何一个人的编程语言,无论你是编程的初学者还是专业人士都可以使用,同时还被宣传可以用于各种各样的场景。但是,1991 年 Python 的出现,彻底粉碎了 Perl 主导编程世界的幻想。

尽管 Python 的语法相比 Perl 稍显繁琐,更加严格,但 Python 让用户使用脚本实现目标的过程变得更加简单直接,这改变了当时的游戏规则。Python 在 2000 年代开始让 Perl 黯然失色,并一度成为编程入门的首选语言。在过去的 15 年中,Perl 的使用量一直在稳步下降,我们看不到任何它将停止下滑的迹象。

5. Objective-C

Objective-C 与 Apple 的关系类似于运动相机与 GoPro 的密切联系——两者都是他们所属公司专用的工具。尽管 Objective-C 已经在 macOS、iOS 和 OS X 开发中使用了二十多年(1996 年至今)的时间,但如今已经很少有程序员在使用该语言,因为 Swift 横空出世了。

Swift 由 Apple 构建并于 2014 年发布,旨在成为 macOS、iOS 及其他几个 Apple 相关平台的新编码标准。Obj-C 在很大程度上是基于复杂的 C 语言创建的,而 Swift 有效地删除了这些复杂的元素,同时增加了一些新的功能特性,例如自动内存管理,对 Cocoa Touch 的支持以及类型安全的集成。

Swift 设法实现和维护了较高的语言稳定性和用户采用率,因为 Apple 公司仍在坚持重写 Obj-C 库,以使旧语言的使用者可以很容易地进行过渡。尽管仍然有很多关于这两种选择哪个更好的讨论,但当前的趋势表明,Objective-C 终将被淘汰。

总结

尽管上述提到的语言似乎都处于下降趋势,但不能保证它们最终一定会消失。如果他们的创造者和社区投入足够的精力来更新和维护它们,添加新功能以及有用的特性,这些编程可能会卷土重来,恢复生命力。但事实是广大程序员更有可能继续涌向更新更好的选择。

如果你正在考虑开始学习哪种语言,那么有许多因素需要考虑。如果你只是将编程当做一种兴趣爱好,你可以聆听一下自己内心并选择最能激发你兴趣的那个来学习,即便所选的编程语言不再被广泛使用甚至面临淘汰。另一方面,如果你正在从事编程相关的工作,那么不应该只选择最流行的编程语言,也不应该赌一把选择未来可能会成为最流行的编程语言。而应该通过仔细对比各个编程语言的功能特性,市场需求以及可见的未来增长潜力等因素后做出选择,这才是明智之举。


原文地址https://dzone.com/articles/5-coding-languages-that-will-disappear-in-10-years


作者 | Program Ace

译者 | 王坤祥

厉害了!2020最好的 10 大国外编程学习网站(文末有彩蛋)

IM即时通讯antrue 发表了文章 • 0 个评论 • 193 次浏览 • 2020-08-31 10:16 • 来自相关话题

在这篇文章中,我收集了10个最佳的编程学习网站,掌握编程技能可能是帮助你走出舒适区的一大步,新手程序员通常会觉得程序员市场的竞争太激烈,工作太有挑战性等。但是,据统计,学习编程是值得你花时间去做的事情,原因如下:软件开发人员的平均工资是103,620。软件开发... ...查看全部

在这篇文章中,我收集了10个最佳的编程学习网站,掌握编程技能可能是帮助你走出舒适区的一大步,新手程序员通常会觉得程序员市场的竞争太激烈,工作太有挑战性等。

但是,据统计,学习编程是值得你花时间去做的事情,原因如下:

软件开发人员的平均工资是103,620。

软件开发市场上有很多职位空缺。

简而言之,软件开发是一个快速发展的领域,在这个领域找工作相对不会那么费劲,除此之外,还有大量的学习资源可以学习,以下就是我最喜欢的编程学习网站:

1.Codegym

Codegym是一个以Java为中心的平台,它比其他平台上都要更加深入的介绍Java,这个网站将理论和实践知识相结合,你可以在上面找到1200多个练习项目。主要具备以下特点:

互动课程,这部分有500多小时的编程课程

内置IDE,帮助你解决实践问题

活跃的Java社区

这个平台通过游戏化和故事化让学习变得有趣

地址:https://codegym.cc/ (支持Google账号直接登录)

这个网站还有一大亮点是,提供了在线IDE ,并且可以通过解决 Task 的方式来学习。当你没解决完一个 Task ,你就可以获得相应的奖励。

2. Treehouse

Treehouse是另一个将编程知识简单化讲解的平台,它会教你处理特定项目所需的技能,比如创建一个APP,创建一个WordPress博客等。Treehouse支持所有流行的编程语言——Java、Python、c++、Ruby和其他语言,具有以下好处:

现实环境中练习问题

与导师面对面的交流

离线访问

地址: https://teamtreehouse.com/ (需要另外注册账号)

3. Khan Academy

尽管Khan Academy没有Java或Python教程,但该平台是理解计算机科学基本概念的一个很好的起点,完成这些课程后,你将会了解数据结构和算法相关的知识,并获得“程序员思维”,这个网站具有以下特点:

免费的课程

涉及前端开发和数据库管理

涵盖常见的算法解答

易于使用的界面

有助于巩固知识的小测试

并且,这个网站目前已经支持简体中文版,但是中文版支持的课程比较少。

地址:https://www.khanacademy.org/computing/computer-science (支持Google账号直接登录)

4. Udemy

Udemy是一个领先的在线教育平台,提供大多数编程语言课程,用户群体也相当大,课程分为收费和免费,这是由讲师决定,价格从10美元到几百美元不等,主要具有以下特点:

广泛的编程语言选择

可以按照自己的节奏获取学习材料

地址:https://www.udemy.com/(支持Google账号直接登录)

5. Code4Startup

如果您想在创业领域获得更多实际经验,Code4Startup是一个很好的平台,可以很好地利用你的开发技能。这个项目允许初级程序员为实际的初创公司编写代码,同时,企业主可以节省招聘人才的费用。

除了作为一个实践场所,Code4Startup还为编程专业的学生提供了大量的免费课程。就我个人而言,Learn Ruby on Rails、Heroku和Wistia API等多个课程我都很喜欢。

**地址:**https://code4startup.com/(支持Google、Github账号直接登录)

6. One Month

如果你为“我如何在一个月内学会一门编程语言?”,请务必查看这个平台。这个编程网站可以帮助初级程序员快速掌握JS、Python和Ruby的基本概念,遗憾的是,该平台上的大多数课程都是付费,尽管少数免费课程,还有一个讨论编程热点新闻的博客。

地址:https://onemonth.com/ (需要另外注册账号)

7. PluralSight

Pluralsight的课程从入门到高级,涵盖了所有流行的编程语言,以及数据科学的基础知识,主要具有以下特性:

课程范围广泛

有专门针对高级开发人员的课程

广泛的订阅计划

地址:https://www.pluralsight.com/codeschool (需要另外注册账号)

8. Coursera

与Udemy一样,Coursera是另一个值得CS和编程学生学习的资源。与Udemy不同的是,该平台关注的是大学层面的深入研究,而不是实践理念。

这个平台是计算机科学专业的一个强大的资源,你可以通过它学习斯坦福大学、密歇根大学等学校的课程。

地址:https://www.coursera.org/ (支持facebook和apple账号)

9. Freecodecamp

这是最大的技术社区之一,拥有相当广泛的教程和课程,这在我看来是其他网站都没法比的,你会在这里找到实用的、基于项目的作业——这就是为什么一旦你掌握了扎实的编程理论,最好还是去Freecodecamp看看的原因,与此同时,Freecodecamp上拥有大量的免费资源。

地址:https://www.freecodecamp.org/ (支持Google、Github等账号直接登录)

10. Codewars

Codewars是一个代码练习网站,能够培养程序员,特别是编程新手的逻辑思维能力,如果你经常对编程提不起动力学习,那么,Codewars你值得拥有,砌墙式进阶的方式会让你对编程更加感兴趣。除了刷题进阶之外,它的社区也值得关注,在这里你可以看到许多困扰着程序员的问题,还有别人解答的解决方案

地址:https://www.codewars.com/


11. GeekOnline

1.png

让我们把目光回到国内,由通信云技术领导者融云推出的开发者社区 GeekOnline 正式与全球极客们见面啦!崇尚科技、自由和创造力的极客精神,GeekOnline 致力于成为一个创意与价值兼备、兴趣和温度并存的技术社区。

这是一个开发者们的技术乐园,也是国内首个全面覆盖了 IM、RTC 等通信云技术的极客社区。在移动互联网概念中,设想所有的客户端都“永远在线”,即每条消息发出必有回响。Online 既是一种态度,也是一种精神,正如 IM 所连接的每一个用户,极客永远在线。

地址:https://geekonline.rongcloud.cn/


原文地址:https://levelup.gitconnected.com/its-time-to-start-learning-coding-top-20-best-websites-to-learn-programming-in-2020-9c5105c76c96