赞
踩
Scrcpy在上一篇博客中有所介绍,并且使用Scrcpy实现了手机屏幕yuv数据的提取([Ubuntu]Scrcpy获取手机屏幕yuv数据_又是谁在卷的博客-CSDN博客)。本文将介绍一个当下较为好用的消息中间件—Zeromq。通过Zeromq中间件对数据进行传输,我们最终通过opencv进行内存的数据读取,并实现连续播放的效果。
往下阅读之前,记得看我的往期博客了解如何提取yuv数据呀([Ubuntu]Scrcpy获取手机屏幕yuv数据_又是谁在卷的博客-CSDN博客),这里就不再过多介绍yuv提取的知识了。接下里就开始实现Scrcpy+Zeromq实现手机屏幕yuv数据传输,并通过OpenCV实现连续播放。
目录
1. Zeromq简单介绍,以及如何与Scrcpy项目源码进行对接(具体思路+代码解析)
1.1Zeromq简单介绍:Zeromq官网(ZeroMQ)
1.2.接下来让我们思考和Scrcpy对接时需要思考的的问题和流程框架。
1.3.3在Scrcpy中创建Zeromq的PUB(发布者)
简单来说,ZeroMQ以嵌入式网络编程库的形式实现了一个并行开发框架。能够提供进程内(inproc)、进程间(IPC)、网络(TCP)和广播方式的消息信道,并支持扇出(fan-out)、发布-订阅(pub-sub)、任务分发(task distribution)、请求/响应(request-reply)等通信模式。与传统的消息中间件,Zeromq大大简化了消息传输的中间爱你过程,具有简介易操作的特点。(这里简单介绍,具体细节请参考官方文档。)
如果看过我的上一篇博客,我们知道yuv数据提取的过程是有顺序的。那我们就迎来了第一个需要思考的问题,
1.我们如何将正确的顺序通过怎样的载体进行运输呢?
这之中可能会有很多有趣的想法(比如yuv分开发送、yuv作为编码为字符串一起直接传输等等)。但是较好的方式是创建一个结构体作为yuv数据的载体,将y、u、v分别作为结构体单独的属性。通过结构体作为载体还有一个好处就是,我们每台设备的分辨率是不同的,分辨率在opencv的脚本中也需要用到(用于还原yuv数据),所以可以放在结构体里一起发送。
载体选择完之后,yuv数据包装完毕之后,在发送之前。我们遇到了第二个问题。
2.通过了解我们知道Zeromq有四个模型,我们需要选取最合适的模型。
Zeromq中最常见的三种种基础模型
1. REQ/REP 请求响应模型
2. PUB/SUB发布订阅模型
3. Pipeline pattern 管道模式
我们要根据需求对模型进行选择。手机屏幕yuv数据的传输是从Scrcpy运行开始之后就源源不断的。也就是说,不管接收者是否受到数据,我们的发送端都不会停止发送。所以显而易见,在这里 2. 发布订阅者更合适。发布者(PUB)设置在Scrcpy的项目端,而订阅者(SUB)则设置在与opencv对接的python脚本中。
选定模型之后,通过Zeromq的传输,在另一端和python脚本对接之后通过opencv呈现即可(后面会有代码解析)
因为yuv数据在scrcpy-master/app/src/decode.c文件中,所以我们将结构体创建在它的头文件中(decode.h)。代码如下图所示:
- # 在decode.h添加如下代码
- # 这里的576*1152是我手机分辨率除以1.875,这里算是压缩的操作
- #define yuv_buf_size 576*1152
- typedef struct{
- int cxt_width;
- int cxt_height;
- uint8_t data_y[yuv_buf_size];
- uint8_t data_u[yuv_buf_size/4];
- uint8_t data_v[yuv_buf_size/4];
- }cxt_frame;
以下代码和提取yuv数据时的操作如出一辙,也是在存有AVFrame的函数中(再次友情提醒:以下代码如果有疑惑,请参考我的上一篇博客)。我们通过memcpy函数将yuv数据分别拷贝到结构体对应的属性中。
- # 创建结构体
- cxt_frame yuv_frame;
- # 定义长宽
- int yuv_width = decoder->codec_ctx->width;
- int yuv_height = decoder->codec_ctx->height;
- yuv_frame.cxt_width = yuv_width;
- yuv_frame.cxt_height = yuv_height;
-
- # buf_size_aline用于对齐,以下存储yuv的操作和上一篇博客中提取yuv的操作如出一辙
- int buf_size_aline = 0;
- for(int i = 0;i<yuv_height;i++)
- {
- memcpy(yuv_frame.data_y + buf_size_aline,frame->data[0]+frame->linesize[0]*i,yuv_width);
- buf_size_aline += yuv_width;
- }
-
- buf_size_aline = 0;
-
- for(int i = 0;i<yuv_height/2;i++)
- {
- memcpy(yuv_frame.data_u + buf_size_aline,frame->data[1]+frame->linesize[1]*i,yuv_width/2);
- buf_size_aline += yuv_width/2;
- }
-
- buf_size_aline = 0;
-
- for(int i = 0;i<yuv_height/2;i++)
- {
- memcpy(yuv_frame.data_v + buf_size_aline,frame->data[2]+frame->linesize[2]*i,yuv_width/2);
- buf_size_aline += yuv_width/2;
- }
-
- # 这里是通过Zeromq进行传输结构体数据的操作,这里暂时先不解释,到后面讲解zeromq时会回到此处进行解析
- zmq_send(responder,&yuv_frame,sizeof(yuv_frame),ZMQ_DONTWAIT);
我们在这之前需要清楚,Scrcpy是多线程的。发送消息的指令可以写在和yuv提取相同的位置。但是创建Zeromq发布者对象的时候不行,因为我们只要创建一次即可。写在函数中会反复创建(无法多次创建,会冲突)。所以我们必须找到这个线程进行初始化操作的地方创建Zeromq对象。在ubuntu中,我们可以在终端输入以下命令通过查找调用函数的位置,一步步向上层寻找。直到找到此线程的初始化位置,查找命令如下:
- # 在此目录下的.c文件中查找名为“push_frame”的位置
- $ find. -name "*.c" | xargs grep -n "push_frame"
接下来在Zeromq中使用的函数可以在这篇博客里找到解析(ZeroMQ教程中文版_神马_逗_浮云的博客-CSDN博客_zeromq中文)
经过查找,我们发现此线程的源头在名为stream.c的文件中,我们将在开始无条件for循环之前创建zeromq对象。代码和输入位置如下:
- #创建新的zeromq环境
- void *context = zmq_ctx_new ();
-
- # 这里有一个responder对象,我是将它初始化在stream.h的文件中了
- # 选择ZMO_PUB发布订阅模型
- responder = zmq_socket (context, ZMQ_PUB);
-
- # 端口号,主机的随便一个可用的端口都行
- int rc = zmq_bind (responder, "tcp://127.0.0.1:5565");
-
- # 如果连接端口失败,则rc返回值为0
- assert (rc == 0);
-
- #最后别忘了在无限for循环之后关闭端口
- zmq_close(responder);
到这里再回去看在存储yuv数据那里的最后一行有发送yuv数据的代码就明白了。
至此我们发布者(PUB)端就设置完毕啦。成功完成Scrcpy和Zeromq的对接,剩下的就是创建python脚本(其他语言也行,我以python为例)接收yuv数据再使用opencv进行播放啦。
剩下内容请关注我的下一篇博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。