scrcpy 源代码渲染逻辑整理

背景

自己学习scrcpy自己写的协同工具遇到了一些问题,所以分析了一下源代码,结果我出现的问题只是参数搞错了。

分析

  1. stream.c
    a. run_stream 创建线程,不停读取数据,然后投递接受的packet,然后投递数据
    b. stream_parse 解析收到数据packet
    c. push_packet_to_sinks 投递数据到
  2. decoder.c
    a. decoder_init 注册解码函数
    b. decoder_push 解析推过来的packet
    c. push_frame_to_sinks 推送得到frame 数据帧
  3. screen.c
    a. sc_screen_init 创建窗口
    b. 绑定frame处理函数
    c. sc_screen_frame_sink_push 投递从decoder解码的数据
  4. video_buffer.c
    a. sc_video_buffer_init 主要初始化队列和一些信号量
    b. sc_video_buffer_start 创建线程,不停读取队列,
    c. run_buffering 不停读取队列帧,判断是否超时,然后进行投递。sc_video_buffer_on_new_frame 通过这个回调到sceen.c 对象里面。
  5. screen.c
    a. sc_video_buffer_on_new_frame 通过 SDL_PushEvent 获取当前数据的

    总结


    这种c代码看起来还是很爽,几个对象抽象挺好,我自己写代码只封装解码层,网络层,SDL处理层,事件通知层。
    scrcpy 队列我没有感受他的好处,开始因为我的卡是没有这个帧的队列,后面发现,根本不是这么一会事情,事情情况渲染速度远远大于传送网络传送过来帧,我自己也实现一个简单队列,检测到帧堆积,直接丢弃,只显示最新的帧,结果从来就没有丢弃。
    scrcpy app 最核心SurfaceControl 反射实现,其他的模拟可以借鉴adb 命令实现代码实现,不过这个SurfaceControl 有人分析android 核心代码可以找到实现,但我自己看了一下貌似没有看懂,直接就拿scrcpy反射实现,其他几个控制命令都是我自己实现,更加简单明了,它是实现几个点击滚动太过于复杂了。

format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);

这个代表帧率,这个Scrcpy其实动态的,这个跟延迟有一定关系,但我之前设置错参数出现反复点击一个表情会出现延迟,然后把这个设置120就延迟了,手机FPS是60.

format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs

这个影响到延迟的,这个给我感觉保持一直采集有数据,之前就是我把这个设置错了,导致快速点击输入表情时候会出现明显的延迟。

format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
这个跟画面清晰度有关系,类似直播高清,蓝光4M 这些。

会删除掉复制tmp的jar包

我开始到目录找了好久,没有好到文件,但命令确实复制到tmp

我不知道scrcpy 用OS.write 有什么好处,我直接用websocket通信,也不走转发,我自己用Websocket跑的时候效果差不多,协议直接用JSON格式就可以了,也不用字节解析,视频流原始传递,这个才是消耗流量的地方,控制的消耗不了多少流量。

scrcpy 我觉得待补充的功能

  • 蓝牙【貌似分支有了】
  • 远程控制【我自己目前这种模式就可以支持远程,我目的就是远程控制手机】
  • 后台运行 【提供增强功能,可以给APP使用,市面已经这样子APP】
  • 输入法增强 【我不知道目前这个实现了没有】
  • 控制栏 【我自己用win32 实现了,感受最原始界面开发】
  • 鼠标点击录制【用来做脚本,结合辅助服务最好,不知道能不能直接反射使用,开启辅助是可以做到】,这个只要做到基本就是无敌了软件。
  • 鼠标模拟增强 【目前鼠标的模拟,如果玩游戏可以检测出来,应该跟属性有关系,这种要做专门优化】

自己开发的软件应该远程控制+手机增强功能,给自己开发用的软件。

app process 其实能做很多东西,
以后想到什么再补充吧。

SDL window 没有焦点第一次点击无效

问题

如题,第一次点击或者窗口失去焦点后,进行点击,不会触发鼠标点击事件,会产生一个鼠标进入窗口的事件。


过程

google 半天中文没有找到类似的, 输入几个英语单词,总算找一个相关的,然后自己调用发现确实有用,同时在scrcpy里面找到这个函数调用,所以确定就是调用这个解决鼠标点击的问题。

解决

SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");

简单设置一次就可以了

wpf 程序在某些win10电脑花屏(显示错乱)

问题

最近接到一个用户反馈他的win10电脑使用我的程序时候出现花屏,然后我远程看确实会出现,然后编译一个单独wpf程序也会花屏。

过程

猜测.net framework 问题

因为他的电脑是win10,自带是4.6框架,我于是下载4.8最新版本,安装重启后发现无效,那么说明跟.net 没有关系

猜测显卡问题

使用360安全卫士的系统修复,点击一键修复,然后检测显卡驱动不是最新,然后更新驱动后发现wpf程序正常了。

总结

  1. 驱动问题,因为渲染基本归系统
  2. 软件注入【网上有说戴尔电脑有注入导致显示问题,我这个用户不是】
  3. 如果上面搞不懂,直接重装系统【基本是万能大法】
    每个人情况不一样,主要有排查思维

android 协同 实时渲染问题 scrcpy的小技巧

android 协同或者直播

我自己开发android的apk直接开启录制屏幕,然后传输H264到PC端,我发现无法实时,但scrcpy却可以做到,我一步步查找代码进行分析

format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 100_000);

Applies only when configuring a video encoder in "surface-input" mode. The associated value is a long and gives the time in microseconds after which the frame previously submitted to the encoder will be repeated (once) if no new frame became available since.

上面goolge的解释,如果没有新的帧就会重复上一个帧,我不知道这么设置后就可以看起来实时,不然大概就是2秒左右的延迟,我自己手机浏览器显示时间就可能明显感觉到。我之前没有用h264编码时候,直接截图都没有延迟,可能就是编码导致的,因为图片没有变化就不会进行编码,可能下次编码就需要等待一段时间,所以产生的延迟了。

h5显示出现延迟

我自己改了好多代码发现网页显示渲染比较慢,代码调整几次都发现有2秒的延迟,无论用画布还是别人渲染方法都不是很好,所以我直接放弃还是用SDL进行渲染。

SDL渲染出现残影(偶尔显示之前帧)

这个问题开始我以为数据传送有问题,我后面直接改成本地文件读取帧,也有问题。
我突然想到可能没有加延迟,导致渲染过快有残影出现,我后面加了SDL_Delay(1) 一毫秒延迟就再也没有出现了。

总结

自己开发的时候减少很多业务代码,scrcpy用了队列,用C的指针来代替工厂类,这种代码不好方便找代码,对自己开发的那确实比较舒服。
用webview显示实时做不到,网上看了好多方案,貌似都不是很好,都基本有2秒左右延迟。

电信申请独立IP过程以及路由使用

背景

自己突然想申请一个独立IP玩玩,比喻远程桌面,直接连接速度还是快很多,而且P2P下载软件有加速功能,虽然我基本不用下载,对于我最有用可能用写一下P2P穿墙的测试代码,所以说中国能够拥有独立IP人不多,P2P网络的梦想可能靠ipv6了。。。

过程

  1. 打电信电话,说自己远程办公
    网上如果不给你办,一定要态度强硬,不然工信部投诉,结果直接给我安排。
  2. 电信人打电话给我,然后给我设置独立IP
  3. 直接网线连接光猫,登录后台useradmin ,密码在猫的后面,查看分配IP确实是Ip138对应的IP,说明我已经分配外网IP
  4. 配置光猫,直接找电信对接人要超级密码,设置DMZ或者桥接。
    所以一定要配置光猫,必须要超级账号,否则没有任何意义,除非开始光猫就是桥接,通过路由器拨号上网,我懒的问账号和密码,所以直接dmz,类似转发,我按照网上的方法都没有搞到密码,所以最后直接发短信的问的。
  5. 路由器设置,既可以设置dmz也可以设置端口转发,我设置端口到另外一个路由器指定端口
  6. 第二个路由器设置端口转到指定IP电脑的端口

    我的网络

    光猫----> 路由器A(wifia)---->路由器B(wifib)----->自己台式机
    为什么2个路由器,因为wifi穿墙不信号强,换几个路由器一样,所以用2个路由器,没有无线wifi中继。

    验证过程

    自己台式机开启一个软件监听指定端口,然后用另外一台电脑,通过外网IP加端口连接,连接通说明就可以了。自己可以用软件,也可以自己写一个网络通信的代码。

    补充

    如果转发端口,一定要设置IP绑定指定MAC,不然IP是动态,一旦IP过期,可能分配别的IP地址。
    光猫DMZ可以设置MAC 设置,这样子就不用IP,不用设置IP绑定问题。
    我的套餐199,如果便宜可能申请不到,我是看网上这么说的,反正我申请下来,没有遇到什么事情。

parsec局域网玩FPS游戏

背景

自己一台笔记本用来开发,另外一台台式机偶尔玩一下荒野行动,用大屏幕玩游戏肯定舒服一点,这样子导致每次都会要切换,导致有一点麻烦

测试过程

  1. 向日葵
    我自己是会员,可以开启游戏模式,结果他无法读取游戏画面,这个应该向日葵采集API的问题,无法采集到显卡渲染的界面。vs 都无无法采集到,因为默认选项用显卡渲染加速,所以无法使用。
  2. 微软远程桌面
    游戏画面是能看到了,结果鼠标一下子飞好远,然后查了一下解决方案,看到微软一个回答,FPS游戏建议用parsec。。。 说明这个无法满足问题。我自己还是用家庭版本,到github找一个安装远程方法搞了半天,结果也无法游戏,只能远程,不过只是远程来看,微软的桌面确实足够了,比其他远程软件要好。
  3. parsec
    直接搜索进行下载、注册、使用。
    直接远程,结果黑屏,然后自己搜索,结果是被远程端必须插入显示器,但我只有一台显示器,于是找了一下解决方案

    parsec 方案解决显示器问题

    购买hdmi虚拟显示器

    这种需要购买一个硬件,插到电脑

    软件,虚拟一个显示器

    貌似国外有开源代码,我觉得麻烦,懒得编译,自己找到一个DisplayAgent软件
    https://pan.baidu.com/s/1vdqm0Is9pjAcG40Qf_q7cw
    不知道这个软件不知道是不是国内,可以试用10分钟,可以购买,貌似才10几块钱。
    如果你联系不到作者,我可以告诉你一个最简单破解方案。
    任务管理器->性能管理,找到软件,然后xx.... 我就不说完了,毕竟有点断人财路。。

vs启动程序设置环境变量

目的

因为经常用第三方库,调试时候我不想复制Dll到运行目录,所以希望找到一个办法解决库的问题。

解决过程

设置环境
设置path=你自己的文件夹目录;
多个用分号分开即可。

补充:
向日葵远程操作的时候vs无法显示,可以在选项里面
如果可用,请使用硬件加速,这个选项关闭即可

POCO c++ 库 使用

背景

c++ 标准库基本没有考虑业务开发,偏离业务太远,对于开发者太不友好,我之前想在使用websocket ,但标准库没有,找了第三方库好久,发现不知道选什么好,c++库引用又不方便,必须编译成自己使用版本,对于我这个老手嫌麻烦,对新手不知道怎么办了.

poco库

这个库已经有10几年了,而且代码开发都是现代c++,代码可读性非常好,开发时候可以学习一下,我都不知道标准库代码为什么写成那样子,基本没有可读性,感觉自带混淆。。POCO 基本涵盖所有方面,加密,压缩,网络,转码 ,json,等等基本想到都有。

POCO 编译

https://github.com/pocoproject/poco

里面写了

自己必须自己安装cmake,我自己用cmake编译的,因为我直接点击sin工程编译总是编译失败,所以建议cmake,这里默认编译64位,windows开发大多数32位,目前我感觉不到64位程序的优势。

  1. 下载,我直接点击下载,我不用git
  2. 收到创建一个cmake-build目录
  3. 在当前目录启动cmd,要切换到当前目录,win11默认启动就当前目录
  4. cmake .. -A Win32 //生成工程,vs 版本必须要支持c++ 14 ,我开始用vs2012编译是不行,我后面用的vs2019, 默认会最高的版本编译,我这里编译32 ,如果你想64 就cmake ..
  5. cmake --build . --config Debug //编译debug版本,不然你开发debug时候,找不到对应库,因为POCO在头文件写lib引入,不同版本引入不一样。
  6. 找到bin debug dll复制到自己要工程即可。

总结

对于c++ windows 开发人来说,不怎么喜欢写命令编译,POCO 貌似有商业版本了,不过我们免费版本就可以了,基本涉及大部分业务。不得不佩服老外毅力,这个东西能写10几年维护,不得不说我们太多缺少这些精神,我们大部分一切只是为了利益。。。

node js 多线程

一、前提

我以前只认为node js 只能单线程,我自己用了一段时间electron 桌面开发,如果遇到CPU密集型业务怎么做呢?我不可能只靠一个CPU来搞业务啊,这个限制不是太大了?

二、解决方法

1:多进程

child_process

等模块创建多进程

这种打补丁方法不是很好,涉及进程通信,写代码效率不高,很容易出现bug。开发者很可能出现变量无法访问的问题,因为多个进程,变量没有共享。不过业务写很分开,也问题不大,多进程资源比较大。

2:多线程

worker_threads

真正用的是多线程,我自己写代码测试,不停创建worker_threads..

测试代码

const { Worker, isMainThread, parentPort } = require('worker_threads');
console.log("hello world:" + isMainThread)

/*if (isMainThread) {
  // This code is executed in the main thread and not in the worker.
  
  // Create the worker.
  const worker = new Worker(__filename);
  // Listen for messages from the worker and print them.
  worker.on('message', (msg) => { 
      console.log(msg); 
    });
} else {
  // This code is executed in the worker and not in the main thread.
  
  // Send a message to the main thread.
  parentPort.postMessage('Hello world!');
}*/

function test(){
    if(isMainThread){
      console.log("自动线程哦");
      const worker = new Worker("./work.js");
    }
   
}

setInterval(() => {
  test();
}, 1000);

通过定时器不停创建工程线程。

work.js

const { isMainThread, parentPort,Worker, threadId } = require('worker_threads');



const min = 2
const max = 1e7

function generatePrimes(start, range) {
    let primes = []
    let isPrime = true
    let end = start + range
    for (let i = start; i < end; i++) {
      for (let j = min; j < Math.sqrt(end); j++) {
        if (i !== j && i%j === 0) {
          isPrime = false
          break
        }
      }
      if (isPrime) {
        primes.push(i)
      }
      isPrime = true
    }
    return primes
 }


console.log("线程id:" + threadId)
generatePrimes(1, 10000000)
console.log("线程id:" + threadId + " 完成")

可以通过资源管理器查看线程【默认不显示,自己设置一下】,线程数不停增加

可以不停增加,因为我这个写法,会很快把cpu吃死。。。。如果像以前nodejs 普通写法,最多只能吃一个CPU..


注意在线程里面不要直接访问对象,毕竟node js 模式就是异步+主线程,引入多线程导致更多问题,多线程仅仅执行,然后丢回主线程即可。

electron 拦截 window.open

一、背景

我之前写一个工具用来客户端加载网页,同时可以注入js,增加功能,类似chrome 浏览器扩展逻辑,但由于网页总是新建一个窗口,我虽然用JS 注入,修改了所有 a 标签的 。但对于动态的无能为力,除非定时器不停遍历,但性能不好,于是就想从electron入手。

二、过程

自己网络查找一些有关的信息,找到一个new-window 事件,这个事件可以捕获新建窗口逻辑。

one_plugin_ui.webContents.on('new-window',(event, url, frameName, disposition, options, additionalFeatures)=&gt;{
      log.debug("new")
      one_plugin_ui.loadURL(url) //在当前窗口打开,如果不想打开,完全想拦截,可以通过url 判断,然后不跳转URL
      event.preventDefault()      
    })