草庐IT

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

又是谁在卷 2023-05-27 原文

        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;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);

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对象。代码和输入位置如下:

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

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

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

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  3. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  6. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  7. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  8. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  9. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  10. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

随机推荐