[Ubuntu]Scrcpy+Zeromq实现手机屏幕yuv数据传输,并通过OpenCV实现连续播放——(二)(思路+代码解析)

        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.代码部分,针对以上结论进行实践

 1.3.1将yuv数据通过结构体进行封装

1.3.2将对应数据放入结构体

1.3.3在Scrcpy中创建Zeromq的PUB(发布者)


1. Zeromq简单介绍,以及如何与Scrcpy项目源码进行对接(具体思路+代码解析)

1.1Zeromq简单介绍:Zeromq官网(ZeroMQ)

        简单来说,ZeroMQ以嵌入式网络编程库的形式实现了一个并行开发框架。能够提供进程内(inproc)、进程间(IPC)、网络(TCP)和广播方式的消息信道,并支持扇出(fan-out)、发布-订阅(pub-sub)、任务分发(task distribution)、请求/响应(request-reply)等通信模式。与传统的消息中间件,Zeromq大大简化了消息传输的中间爱你过程,具有简介易操作的特点。(这里简单介绍,具体细节请参考官方文档。)

1.2.接下来让我们思考和Scrcpy对接时需要思考的的问题和流程框架。

        如果看过我的上一篇博客,我们知道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呈现即可(后面会有代码解析)

 1.3.代码部分,针对以上结论进行实践

 1.3.1将yuv数据通过结构体进行封装

        因为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;

1.3.2将对应数据放入结构体

        以下代码和提取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;idata[0]+frame->linesize[0]*i,yuv_width);
        buf_size_aline += yuv_width;
    }

buf_size_aline = 0;
    
for(int i = 0;idata[1]+frame->linesize[1]*i,yuv_width/2);
        buf_size_aline += yuv_width/2;
    }

buf_size_aline = 0;
    
for(int i = 0;idata[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);

1.3.3在Scrcpy中创建Zeromq的PUB(发布者)

        我们在这之前需要清楚,Scrcpy是多线程的。发送消息的指令可以写在和yuv提取相同的位置。但是创建Zeromq发布者对象的时候不行,因为我们只要创建一次即可。写在函数中会反复创建(无法多次创建,会冲突)。所以我们必须找到这个线程进行初始化操作的地方创建Zeromq对象。在ubuntu中,我们可以在终端输入以下命令通过查找调用函数的位置,一步步向上层寻找。直到找到此线程的初始化位置,查找命令如下:

# 在此目录下的.c文件中查找名为“push_frame”的位置
$ find. -name "*.c" | xargs grep -n "push_frame"

 接下来在Zeromq中使用的函数可以在这篇博客里找到解析(ZeroMQ教程中文版_神马_逗_浮云的博客-CSDN博客_zeromq中文)

经过查找,我们发现此线程的源头在名为stream.c的文件中,我们将在开始无条件for循环之前创建zeromq对象。代码和输入位置如下:

[Ubuntu]Scrcpy+Zeromq实现手机屏幕yuv数据传输,并通过OpenCV实现连续播放——(二)(思路+代码解析)_第1张图片

#创建新的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进行播放啦。

剩下内容请关注我的下一篇博客

你可能感兴趣的:(ZeroMQ,python,c++,计算机视觉)