OBS的插件-画板实现(基于Qt)

2023年8月28日 评论已被关闭

简介

OBS基于OpenGL和D3D11实现了一个通用的图形库,可以利用这个图形库来实现简单的画板,至于更复杂的画板实现的原理也是一样的。实现图如下。

 原理

说明一下,因为OBS自带的做图非常麻烦且无法满足要求,这里使用Qt自带的QPainter进行内存做画,然后输出RGBA像素数据。

在使用插件之前我们要对插件进行注册

实现方式一:obs_source_output_video

使用异步视频源,注意插件的output_flags使用OBS_SOURCE_ASYNC_VIDEO这个标志,当画面需要更新时,使用obs_source_output_video将帧输出源中。

大致代码如下:

上述代码很好地展示了绘制后立即输出到源上,但上面的代码有一定的性能问题。

问题:当鼠标事件瞬时非常多时,会出现fps过高导致CPU和内存暴涨,那么这时候,我们可以加一个定时器解决,比如我们限定最多30fps。

我们修改了updateRect代码,使得将更新的频率固定住,这样不管鼠标事件多么的多,我们最多只会33ms更新一次(fps=30)。

上面的代码看上去比之前还要好了,但还是有优化的空间,因为每一次更新的是局部图片,但是输出却是完整的像素,会重复拷贝内存像素数据到显存,分辨率较小时体现不出来,一是分辨率稍微大一点也会有严重的内存和CPU性能问题,此时这种方法已经无法满足要求了。

实现方式二:使用纹理

第一种方式使用了异步视频源,这种方式在需要的时候输出帧就可以了,但是有一定的性能问题,那么可以可以使用纹理贴图的方法。如果要使用纹理贴图我们首先要指定插件的渲染回调函数。

接下来,我们将将内存数据贴到纹理上。

这样就实现了对纹理的贴图,但是上面的方法还有一个性能问题。问题在于,只要渲染就将所有像素数据拷贝到显存,显然是没有必要的。我们应该使用局部更新,在需要更新的时候将那些需要更新的像素拷贝到显卡。也就是dirtyRect那个部分。很遗憾的事OBS没有这个接口,所以我们自己实现一个。

在实现之前我们看一下全局更新的代码,gs_texture_set_image如下。

通过观察代码,我们看到obs使用内存映射的方式进行拷贝,并且区分了是否翻转,这里我们没有这个需求,正着拷贝就可以了,所以我们就可以按照上面的方式使用局部拷贝。核心操作无非就是按行复制,需要注意整张图的行大小和局部矩形的行大小,以及起始地址的对齐。

然后在调用的时间传入dirtyRect就可以了。

到此,所以优化就结束啦。

实现

代码整理中,即将扩充各种图形,联系nie950@gmail.com

分类: C/C++, OBS, Win32 标签:

nginx进程管理-signaller进程

2020年5月25日 评论已被关闭
nginx控制进程用来控制master进程,我们知道nginx有四种类型:
位于:src/os/unix/ngx_process_cycle.h

当我们要对nginx升级、重启、切割等待日志时,这是nginx的角色为NGX_PROCESS_SIGNALLER

也就是说在我们使用nginx -s 信号时这时启动的nginx属于NGX_PROCESS_SIGNALLER类型了,做为signaller的nginx它的作用是对master的管理,信号类型存储在ngx_signal这个变量里,它的用法是这样的:

当程序启动时我们指定了合法的-s参数时,ngx_process被设置成了NGX_PROCESS_SIGNALLER,接下来的初始化函数ngx_init_cycle中,识别了NGX_PROCESS_SIGNALLER这个类型会把创建pid,创建文件,打开端口等等这些全部忽略,只调用了解析配置和各个模块的初始化配置的回调函数,所以我们在写core模块时一定要注意不要在ini_conf回调函数打开资源。

我们可以看到在初始化时如果是信号进程会跳过一些系申请统资源之类的操作,同时也会初始化最外层的core模块的配置。在所有配置解析并初始化完成后,信号进程开始给master发送指令了,这过程就是读取配置后的pid文件里的进程ID,然后通过进程ID给master进行发送信号了。

如果只是发送信号,从代码里了解到下面是等价的:(假设master的进程号是12345)

至于为什么要用这些信号呢或者大家可以思考一下,这两种方法的利弊在什么地方。

分类: C/C++, Linux, Nginx 标签:

使用chroimum QUIC构建QUIC客户端程序

2020年4月22日 评论已被关闭

我将QUIC异步使用的方法整理了一下:

 

第一步: SOCKET连接

第二步: 设置SOCKET读写包处理器

第三步:创建QUIC会话并握手连接

第四步: 实现相关回调

第五步:开启一个线程进行异步操作

第六步:设计API

有不对的地方欢迎大家指正:nie950 at gmail dot net

分类: C/C++ 标签:

使用Visual Studio2015调试云主机PHP代码

2017年4月23日 评论已被关闭

以下要用到的所有软件都可以在下面的地址(仅办公网访问)下载得到。

http://xesoa.com/app/vs2015

http://xesoa.com/app/ssh

 

调试流程

  1. 安装visual studio 2015

下载并安装:http://xesoa.com/app/vs2015/vs2015.pro_chs.iso

步骤略

 

  1. 安装1.23.9750

下载并安装:http://xesoa.com/app/vs2015/va_x_2073_setup.zip

步骤略

 

  1. 配置云主机

3.1配置xdebug

3.2 php.ini配置如下

  1. 配置ssh(以exe为例)

这里要做一个说明:如果在云主机上开发,因为云主机是无法连接到办公区域的。所以要做一下隧道转发。对ssh感兴趣的可以参考:http://www.ietf.org/rfc/rfc4251.txt

如果对转发原理不感兴趣可直接跳到操作步骤

ssh转发包括两个正向转发和反向转发,动态转发(略)。

 

正向转发例子:

凡是发往本机9000端口的都能过ssh.xesoa.com主机的22端口转发给www.163.com:80,这就实现了一个简单的正向代理请求。注意:localhost在本机解析,www.163.com在ssh.xesoa.com上解析。

反向转发的例子:

凡是到达ssh.xesoa.com主机端口11011的数据都通过ssh.xesoa.com的10026端口转发到主机是10.99.1.11的22端口上。注意:*代表主机ssh.xesoa.com上的任意IP地址。

因为云主机(10.99.1.124)无法连接上办公区域(172.88.1.126),所以无法正向发给办公区域,所以可以在通过反向转发在172.88.1.126上运行:

所有到达10.99.1.124的9000端口的数据都通10.99.1.124的22端口转发到172.88.1.126的9000端口上。调试过程变成下面过程:

操作步骤:

4.1下载plink, windows下ssh的一个实现。

http://xesoa.com/app/ssh/putty/plink.exe

4.2 在cmd里执行

正常连接到远程云主机可以看一下,出现9000端口表示转发绑定成功。

(备注可以把它做成一个开机启动项。)

  1. 配置vs2015

在这里要说明的是:所有的代码操作(svn提交下载)都在本地,不应该在云主机修改php,所有在本地的修改应该即时的同步到云主机上。

  • 新建php工程

指定根目录,这个目录将会与云主机同步。

设置云主机同步目录。项目->属性

Site path是指网站的根目录,每次调试会将本地同步到该目录。

点击publish发布到云主机上。

  • 发布代码

在你修改的文件上右键publish就可以将修改推送到云主机上。

  • 设置断点

在你需要的地方,设置断点,使用F9。

 

  1. 开始调试

按下F5就可以开始调试,如果运行到断点,程序会自己停下来检查。

在浏览器或者postman就可以调试我们的代码,大致窗口如下。

快捷键:

F9设置/取消断点

F10下一步

F11进行函数

F12 转到函数定义

Shift+F9,查看变量值

Ctrl+F5停止运行和调试

 

 

 

 

分类: 翻译 标签:

Nginx使用instance标记处理kqueue/epoll中的stale事件

2015年8月28日 评论已被关闭

下面以epoll为例:

每个fd往往会关联一个自定义的结构体来表示逻辑上的连接。比如在nginx中每个ngx_connection_t都抽象成一个TCP连接并且与fd关联。

当我们accept到一个连接的时候,我们会申请这样一个ngx_connection_t,随后将这个fd与ngx_connection_t关联并加入到epoll循环。

代码(添加读事件)

然后我们使用epoll_wait来检查发生的事件

每个epoll_event的data.ptr是由ngx_connection_t*指针与instance相或而来,也就是data.ptr = c | instance;这样做是有前提条件的,那就是c必须是偶数,因为instance是bool类型,data.ptr最末位存储了instance的值。

 

假设当某一次epoll_wait后返回了一组epoll_event。

随后进行了如下操作:如图1所示:

(1)fd=47的文件描述有事件发生。

(2)处理fd=47时,我们关闭了fd=12的连接,回收了ngx_connection_t内存。

(3)接着处理fd=12时,此时data_ptr指向的ngx_connection_t已经被销毁,访问data_ptr将出现致命错误。

20150828160701

解决方法(如图2所示):暂不回收ngx_connection_t内存而是将ngx_connection_t的fd设置为-1,那么处理fd=12时,发现ngx_connection_t中的fd=-1,那么足以证明这个fd在之前已经被释放掉了,从面阻止使用data_ptr继续使用,这就是stale事件。

20150828160702

 

当我们将fd=12的ngx_connection_t回收后将fd设成了-1,防止处理到fd=12时使用了被释放的data_ptr。

试想一下,此时将fd设置成了-1,但在处理fd=12前,我将刚才释放的ngx_connection_t重新分配给了新连接fd=48(调用了accept()),那么在处理fd=12时,其data_ptr仍然不能使用,因这个ngx_connection_t已不属于fd=12而是属于fd=48,也就是说fd=12的ngx_connection_t已被释放并被fd=48使用。

我们使用instance来区分这个ngx_connection_t到底属于谁。因为本身data_ptr中存储了instance值,并且ngx_connection_t中也存储了instance值,在申请ngx_connection_t时,我们要将ngx_connection_t中的instance值置反。当ngx_connection_t的instance与data_ptr不一致时,说明当前fd已销毁。(如图3所示)

20150828160703

 

注意

另外,有一点要说明的是,如果在处理fd=12之前,fd=48也被销毁了,然后该ngx_connection_t又被fd=49复用,那么这种情况仍然会有问题。

这会导致data_ptr中的instance值与ngx_connection_t一致(两次置反)并且fd=49。event_list原本指示的fd=12有事件,但被nginx错误地认为了fd=49有事件发生,目前我还没想到好的解决办法。

分类: C/C++, Linux, Nginx 标签:

nginx的模块上下文

2015年4月26日 评论已被关闭

ngx_modules数组定义:

初始化完成: 阅读全文…

分类: Nginx 标签:

nginx中定时器与事件流程

2015年4月13日 评论已被关闭

1.nginx中的时间更新

Nginx在内部自己缓存时间,那么这些时间值在什么地方更新呢?

1.1 收到信号时更新时间

1.2 timer_resolution值为0

如果用户没有设置timer_resolution值或者设置为0值,那么每次在轮询事件都会设置NGX_UPDATE_TIME标志,表示轮询结束后要更新时间(以kqueue为例):

轮询时:

1.3 timer_resolution值非0

如果用户设置timer_resolution为非零值,nginx会在定时器触发时更新时间。

在nginx中系统定时器的具体实现有两种,一种是使用setitimer(),另外一种具体事件中模型实现的。

1.3.1使用setitimer()

在事件初始化后使用setitimer(timer_resolution),那么系统会每隔timer_resolution(单位为毫秒)发送SIGALARM信号,信号处理函数:

后续的的轮询会被打断并在结束后会去判断ngx_event_timer_alarm值,如果为1的话会更新时间(以kqueue为例):

1.3.2使用特定的事件模型

对于特定的事件模型,在这里只包括kqueue和eventport。它们内部实现了一个定时器,我们在事件初始化时安装定时器,例如(以kqueue为例):

同时设置还会置上NGX_USE_TIMER_EVENT标志,并且定时器触发时没有回调函数,我们要在事件轮询后判断是否是定时器事件,如果是则要更新时间(以kqueue为例):

 

总结:nginx自身缓存时间。 阅读全文…

分类: Linux, Nginx 标签:

二叉树的深度优先遍历与广度优先遍历

2014年11月22日 评论已被关闭

二叉树的深度优先遍历就是二叉树地前序遍历.二叉树的广度遍历就是层序遍历.(最后面有完整的代码).
一.深度优先遍历(DFS)

深度优先遍历与前序遍历的结果是一样的.那么递归实现应该是

与之对应的非递归算法是,使用栈的先进后出的特性.

二.广度优先遍历(BFS)

二叉树的广度优先遍历其实很简单,大概的思路是访问一个结点时,将它的子结点放入队列后面,当本层访问结束后,队列中就剩下下一层的所有结点,直到队列中没有结点为止.
可以看到队列的前部是当前层剩下的结点,队列的后部是当前层已经访问过的结点的子结点.

完整代码 阅读全文…

分类: 算法导论 标签:

Win32选择保存文件路径和打开文件夹的函数

2014年9月30日 评论已被关闭

有时候我们需要拾取打开或者保存的路径,浏览文件夹,下面是win32的接口。

MSDN:GetSaveFileName
MSDN:SHBrowseForFolder

分类: Win32 标签:

rtmp直播流程

2014年6月24日 评论已被关闭

下面是通过RTMP抓包所得,左侧是客户端Flash Media Live Encoder,右侧是服务器Flash Media Server

 

分类: C/C++, Linux 标签: