仅用5个线程,让Idea全系列Ide能看电视直播电影听广播音乐美女图
Posted miukoo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仅用5个线程,让Idea全系列Ide能看电视直播电影听广播音乐美女图相关的知识,希望对你有一定的参考价值。
前言
好久没有写关于技术类型的文章了,很多朋友都催我写写,这也快到年底了,所有抽了点时间写一篇,希望能借此来回馈大家在过去一年中对我的支持。
PS:本文中有3张GIF效果图太大,没办法上传,追求看完美效果的朋友可以摆驾链接:百度网盘 请输入提取码 提取码:xad4 插件高清GIF效果。
需求
今天要分享的技术方案是我在写bg-boom这款插件当中一小块的代码实现方案,先从需求说起吧,其模块整体的需求是丰富idea的背景功能,具体需求为:
-
让idea背景功能支持图片
-
让idea背景功能支持gif图片
-
让idea背景功能支持视频(既动态桌面的效果)
-
让idea背景功能支持广播收听
-
让idea背景功能支持TV在线收看
-
让idea背景功能支持直播在线收看(要支持MMS、RTMP、RTCP、RTSP、M3U8等直播流协议)
-
让idea背景功能支持音乐在线播放(既实现在线音乐播放器的功能)
-
让idea支持从网页中自动爬取图片、视频、直播流等资源自动播放
-
让idea所有产品都能够支持以上功能,包括但不限于IntelliJ IDEA、WebStorm、DataGrip 、android Studio、CLion 、phpStorm 、PyCharm等产品
-
让idea在linux、window、mac平台上也支持以上功能
效果
以上需求文字理解较为晦涩难懂,就先看看最终的实现效果,感受一下需求最终实现的效果吧:
PS:由于gif太大,在这里我只放了一张网络视频的效果图,要听声音的朋友可以转看,效果更佳: Idea炫酷的视频背景插件Bg-boom_哔哩哔哩_bilibili
现状分析
简单理解以上需求后,总是要撸起袖子干事情的,就先从idea现状对背景的支持开始吧,经过一番源码的阅读操作,最终可以发现idea背景功能支持有些简单,如下:
-
idea自身支持背景功能(万幸)
-
idea只支持背景图片(非常遗憾视频不支持)
-
idea支持图片的翻转、平铺、透明度等样式微调
2021.2.3版本的背景功能截图如下(不同产品和版本可能存在差异):
架构设计
通过对idea现状分析后,可以清晰的得出一个结论就是:要实现自己的需求,那么基本是对背景功能的重新实现。Do it!
第一步:拆分需求功能
先归纳一下在需求描述中对于实现的关键点,是让idea支持图片、gif图片、视频、直播、广播、在线音乐的支持。就这样理解还是不够直观,那就再做一次抽象转换:
-
图片=图片
-
gif图片=图片+图片(PS:是由多帧图片组成)
-
视频=图片+声音(PS:视频就是由多帧图片和声音组成)
-
直播=图片+声音(PS:直播就是由多帧图片和声音组成)
-
电影=图片+声音(PS:电影就是由多帧图片和声音组成)
-
广播=声音(PS:广播就是由声音组成)
-
音乐=声音(PS:音乐就是由声音组成)
经过这次转换我们就可以清楚的发现,需求中提出的功能实际对于idea来说就是单独放图片、单独放声音、同时放图片和声音的需求,是不是顿时简单好理解一大半。
总结:拆分后的需求就是要求idea可以背景播放图片和声音,图片和声音可在一定条件下单独开启。
第二步:架构设计
-
理解拆分需求之后,软件需要实现图片和声音的同时播放,因此可以先架构两个并行的线程:
-
声音播放线程:主要用于输出声音数据到声卡
-
背景播放线程:主要用于输出图片到idea背景
Q1:背景图片播放能重用Idea自动的背景功能吗? A1:通过源码的分析,Idea背景图片的功能,存在深度缓存,并且存在系列不必要的计算,在性能上不太满足视频的播放需要,因此背景功能也需要重实现。
-
-
在idea中图片可以直接支持,但是视频、流需要自己实现解析,因此还需要架构两个线程来完成视频、流的解析工作:
-
多媒体解码线程:主要用于获取视频、直播、音乐、TV、当中的图片和声音解码,并推送给多媒体播放线程播放
-
多媒体播放线程:主要实现一个播放时钟,到播放点把声音和图片推送给对应线程进行播放处理
Q2:为什么不在解码线程中直接推送声音和图片播放? A2:简单说,10M的视频,解码指需要1s钟,但是播放可能需要持续3分钟,这个过程中性能处理是不对等的;再加上多媒体中声音和图片存在数据交叉情况,最终还是分成2个线程来处理,较为简单易维护。 Q3:多媒体播放线程只是到点推送播放数据的功能吗? A3:多媒体播放线程除了推送播放数据的功能,其实还有一个非常重要的功能,就是协调声音和图片画面的同步问题;因为在声音和画面在并行两个线程中执行,一个线程执行慢一点,就会导致声音和画面的不一致,这种现象当然需要考虑并解决。
-
-
在实现过程中,由于性能和体验的权衡,最终把方法调整成为用5个并行线程来实现整个功能。
-
背景刷屏线程:主要把背景图片绘制到idea背景显示
-
背景播放线程:调整后,最终只会把要输出的图片放置到背景缓存区中,不负责显示图片
Q4:为什么增加背景刷屏线程? A4:在调优过程中,发现idea背景输出引起的GUI界面刷新不能在短时间内完成,因此画面延迟出现的情况出现较为频繁。为解决此问题,设计一个背景缓冲区,缓冲区内的图片只存储即将显示的那张图片,如果背景刷屏线程过慢,就会导致缓冲区中的图片被新的缓存图片所覆盖,这样的效果恰是画面延迟后跳帧显示画面的效果。因此巧妙的实现了当前idea画面刷新延迟时,跳帧显示图片,以达到声音和图片同步的效果。因此增加此线程,即为明智。
-
看到这里,不容易,看会电视先休息一下吧~~
技术选型
以上需求要实现,用到的技术其实并不多,简单介绍一下以上方案的关键技术选型:
-
视频解码:选型使用的是javacv;(ps:java对视频解码支持并不完善,只能借助javacv完成,实际上javacv是封装了ffmpeg)
-
线程同步:这里5个线程之间需要协作同步,主要使用ReentrantLock锁来同步
-
画面显示:画面显示不需要特殊技术,重新Idea的PaintersHelper即可,但由于idea源代码的作用域限制,实现改步需要使用大量反射操作来完成
技术实现
由于实现的代码过多,再这里就不粘贴出来了,感兴趣的同学可以到我个人订阅号领取:
-
扫描订阅
-
在订阅号
-
回复【视频代码】4个字领取以上功能的多线程实现源码
-
回复【动态背景】4个字查看效果演示视频
-
性能优化
感兴趣代码实现的朋友可以同上述方式,自行领取资料。在这里我主要分享一下实现完成功能之后对代码的优化过程。
01、完成第一版发现问题
功能基本实现完成之后,发现运行效果能达到预期,但是CPU占用非常高,如下图:
-
下图中带有 madou、javacv、ffmpeg都是自身程序的实现,发现占用CPU非常高
02、拆包定位性能消耗点
在com.madou.app.vedio.core和thread包中都有多个实现类,不够直观发现那个类会有问题,因此建立多个包,每个包下只放一个类文件,再次运行:
PS:下图中第一次CPU占406%,第二次占270%,这是由计算机自身的状态产生的偏差,在此忽略。
从下图可以看出:
-
com.madou.app.vedio.print占用非常高,其下面的类主要用于把图片刷新到idea背景区域显示
-
com.madou.app.vedio.audio占用非常高,其下面的类主要是进行声音解码和声音播放的代码
03、原因分析
-
先分析com.madou.app.vedio.print包,其下面只有一个用于把Image绘制到Idea背景区域显示的程序,非常简单,貌似此代码的优化空间不太大,先跳过。
private void drawGraphics(Image image, Graphics2D g, Rectangle dstBounds) if(image!=null) g.drawImage(image,dstBounds.x,dstBounds.y, Double.valueOf(dstBounds.getWidth()).intValue(), Double.valueOf(dstBounds.getHeight()).intValue(),null);
-
解密帧数据
public byte[] parseToByte(Buffer[] samples) if(samples==null) return null; int k; buf = samples; switch(sampleFormat) case avutil.AV_SAMPLE_FMT_FLTP://平面型左右声道分开。 leftData = (FloatBuffer)buf[0]; TLData = floatToByteValue(leftData,vol); rightData = (FloatBuffer)buf[1]; TRData = floatToByteValue(rightData,vol); tl = TLData.array(); tr = TRData.array(); combine = new byte[tl.length+tr.length]; k = 0; for(int i=0;i<tl.length;i=i+2) //混合两个声道。 for (int j = 0; j < 2; j++) combine[j+4*k] = tl[i + j]; combine[j + 2+4*k] = tr[i + j]; k++; return combine; case avutil.AV_SAMPLE_FMT_S16://非平面型左右声道在一个buffer中。 ILData = (ShortBuffer)buf[0]; TLData = shortToByteValue(ILData,vol); tl = TLData.array(); return tl; case avutil.AV_SAMPLE_FMT_FLT://float非平面型 leftData = (FloatBuffer)buf[0]; TLData = floatToByteValue(leftData,vol); tl = TLData.array(); return tl; case avutil.AV_SAMPLE_FMT_S16P://平面型左右声道分开 ILData = (ShortBuffer)buf[0]; IRData = (ShortBuffer)buf[1]; TLData = shortToByteValue(ILData,vol); TRData = shortToByteValue(IRData,vol); tl = TLData.array(); tr = TRData.array(); combine = new byte[tl.length+tr.length]; k = 0; for(int i=0;i<tl.length;i=i+2) for (int j = 0; j < 2; j++) combine[j+4*k] = tl[i + j]; combine[j + 2+4*k] = tr[i + j]; k++; return combine; default: break; return null;
-
再分析com.madou.app.vedio.audio包,其PlayAudio类主要是用于解密帧数据和播放帧数据、还有检查声卡缓存剩余数据
-
播放帧数据
public void play(byte[] data) if(sourceDataLine!=null&&data!=null) sourceDataLine.write(data,0,data.length);
-
检查声卡剩余播放数据
public boolean shouldLoadVoice() if(sourceDataLine!= null&&sourceDataLine.isRunning()) return sourceDataLine.available()>sourceDataLine.getBufferSize()*9/10; return false;
-
04、声音解码优化
从以上分析可以初步定位到声音解码最有可能引起CPU占用高。因此优先对此优化。那么优化的思路是怎样的呢?大致有以下几种方式:
-
从算法上优化:声卡数据基本都需要单字节的进行处理,所以此方式基本优化空间不大(直接PASS)。
-
从内存上优化:提前把声卡数据一次性解码后缓存起来,这样再播放的时候就不再占用CPU解码了(唯一方案,Do it!)。
经过以上分析,从内存上缓存来减少CPU的使用的方案是优先方案,因此先证明一下可行性。
04.1 测试几个视频的声卡数据,看缓存方案是否可行
先提取几个视频的声卡数据,通过大小判断一下是否内存能够缓存存储,于是测试了3个视频,结果如下:
序号 | 视频大小 | 声卡数据大小 |
---|---|---|
1 | 3M | 1.7M |
2 | 122M | 34M |
3 | 6G | 1.8G |
通过上述简单的测试,我们的需求中定位的视频大小在100M以内,声卡在34M左右,一般电脑的内存都能够完全存储,因此内存方案基本可行。但是如果万一有用户弄4K高清视频怎么办呢?上G的声卡数据并不是每个用户都能负担的,因此完全缓存又似乎不可行。
小插曲:比如下面有哥们真用idea真试了试播放了4K高清的007电影,电影文件6.87G,观赏一下效果吧~~~
没错上面放4K高清电影的哥们就是本人,作为一个技术控,绝不容忍IDEA不能放电影做背景,因此必须解决大视频声卡缓存数据的问题。那么怎么解决了,如果声卡数据都缓存,内存不够,不缓存CPU又高。这种情况呀就只能找一个平衡点,依然使用内存缓存声卡数据,但是要设置一个缓存上限,防止大视频声卡数据超出 JVM 堆大小,进而引起GC,GC导致画面卡顿。但是选这个缓存上限应该设置多少呢?每个用户的内存大小不相同,这个上限点不好定义,怎么办?可不可以动态设置内存缓存空间大小呢?答案是可以的。
经过上述纠结后,最终定的方案是依然使用内存缓存声卡数据,并依据空闲内存大小动态设置缓存上限,防止大视频声卡数据超出JVM堆大小,进而引起GC,GC导致画面卡顿。
内存上限计算公式为:动态缓存上限帧数 = JVM 空闲内存大小 / 2 / 每帧音频大小中值
以上公式的基本理解是,把用户电脑中 JVM 一半的空闲空间拿来作为帧数据缓存空间,并除以平均每帧音频大小,即得到总的缓存帧数。
04.2 测试几个视频的每帧音频大小
通过以下粗糙的测试,简单取65KB作为平均每帧音频的大小。
序号 | 视频大小 | 声卡数据大小 | 每帧音频大小 |
---|---|---|---|
1 | 3M | 1.7M | 20KB |
2 | 122M | 34M | 73KB |
3 | 6G | 1.8G | 65KB |
04.3 实现动态音频缓存
明白以上计算公式之后,其实实现就较为简单了,但是在这里一定要提醒一下各位,JAVA中缓存的实现一定要用软引用去实现,以保障在IDEA程序大量使用内存的时候可以有效的释放音频帧的缓存,毕竟IDEA的功能是优先需要去保障的。
/**
* 缓存音频资源,减少CPU资源的消耗
*/
SoftReference<ConcurrentHashMap<Long,PlayFrame>> voiceLocalCache = new SoftReference<ConcurrentHashMap<Long,PlayFrame>>(new ConcurrentHashMap<>());
/**
* 动态计算最大的缓存数量
*/
volatile int maxLocalCache = 0;
/**
* 动态计算最大的缓存数量,在第一次播放完成后,动态调整计算
*/
volatile int avgLocalCache = 0;
04.4 验证第一版本优化思路
下图中标记为3的图片就是优化后的性能,发现CPU有所下降,但是不明显,难道我们搞错了?
05、最终优化
经过对上述声音解码的优化效果并不明显,那么说明程序CPU消耗点并不在此上面。因此继续分析,上面提到的声卡就是写数据,不可能存在性能问题,即使存在也优化不了,所以先跳过,再排查"检查声卡剩余播放数据"的程序,通过日志打印的方式,发现上层「多媒体播放线程」每微妙就会调用"检查声卡剩余播放数据"程序,导致CPU过高。(PS:多媒体播放线程中是一个循环计时程序,每循环一次就检查一次声卡是否还有播放的数据,如果不够,就立即写入新的声卡数据,实现逻辑是正常的)。定位到这个问题之后,其实解决起来就比上述优化简单多了,直接通过休眠线程来减少对"检查声卡剩余播放数据"程序的调用即可,最终优化后成功降下了CPU指标。
结束
经过上述优化,插件性能指标能够基本使用,但是CPU还是占用太高,所以优化会再继续,无止境!有意思的是我和迅雷影音比较过CPU使用率,在播放开始的时候CPU消耗差不多,但是随着播放的时间推移,迅雷影音就会下降一半CPU占用率。这是否得益于迅雷影音使用了硬件加速?怀揣好奇的心态尝试了把java中BufferedImage图片换成了支持硬件加速的VolatileImage图片,发现JAVA中死活都开启不了硬件加速,就等于VolatileImage的性能是无效的,研究了一天,无结果,就先把这个版本先提交了吧!下个版本再来优化~,点赞~ 收藏~ 一起加油吧~
以上是关于仅用5个线程,让Idea全系列Ide能看电视直播电影听广播音乐美女图的主要内容,如果未能解决你的问题,请参考以下文章
IDEA插件系列(100):CPU Usage Indicator插件——显示CPU使用情况
IDEA插件系列(55):WakaTime插件——自动生成编程项目中的指标见解和时间跟踪
我的Android进阶之旅JetBrains全家桶系列的IDE,比如Android Studio,IDEA,PyCharm之类的使用Git超慢的问题的解决方法
我的Android进阶之旅JetBrains全家桶系列的IDE,比如Android Studio,IDEA,PyCharm之类的使用Git超慢的问题的解决方法