scrcpy实现手机屏幕与ubuntu共享、推流、实时分割(wifi环境下)

本文介绍了如何在Ubuntu系统上使用scrcpy进行手机视频的无线投屏,并对其源码进行修改,以便将YUV流分割并通过本地端口广播,结合ZeroMQ实现与深度学习模型(如Unet)的实时交互,进行视频分割处理。过程中涉及源码下载、编译、错误解决以及Python代码示例,展示了从视频捕获到实时处理的完整流程。
摘要由CSDN通过智能技术生成

有些时候我们需要将手机上的视频投到ubuntu上并进行处理,scrcpy就是一个非常低延时并且可以wifi共享的工具,本文主要针对其源码进行修改,实现ubuntu上实时分割手机画面,分为安装编译源(大坑)、修改源码、修改分割推理代码三部分。

scrcpy的使用和wifi连接可参考

Ubuntu安卓手机投屏_zekdot的博客-CSDN博客

源码阅读和前两个部分我是参考这篇博客的,但中间还是不少坑:Scrcpy源码的阅读及在Ubuntu上的实现(二)——获取手机屏幕yuv数据_郑烯烃快去学习的博客-CSDN博客

一、安装编译源码

后面的源码修改有两个重要文件需要修改:stream.c和decoder.c,前者是用来把yuv流推到深度学习那边去的,后者是为了从scrcpy中把流推出来。如果你按照其他博客下载,即使跌跌撞撞编译成功,但发现没有这两个文件,太狗了,我这边找到最近的有这两个文件的是scrcpy1.22版本,千万别下错了!!!。需要下载的文件有两个:scrcpy源码和server

源码链接:https://github.com/Genymobile/scrcpy/tree/v1.22

server链接:Release scrcpy v1.22 · Genymobile/scrcpy · GitHub

下载完成后,解压scrcpy,善后meson build生成build文件夹,build-app里面把scrcpy-sever放进去,此时在build下运行ninja -Cbuild,此时如果会报错,如果报错消息是server没生成,此时去看看build-app里面有没有可执行文件,如果有那就编译结束了,./scrcpy运行即可

如果此时报错出现什么版本不一致或者其他错,进到/usr/local/share/scrcpy中把server往这里面拷贝一份。

至此,第一步编译运行源码完成。

二、修改源码

源码最重要的就是输出yuv流并通过本地端口广播出去,进入app/src下。修改decoder.c和stream.c以及对应的h文件,需要修改的部分如下:

decoder.c-----输出yuv流

#include "decoder.h"
#include <zmq.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <stdio.h>
#include "events.h"
#include "video_buffer.h"
#include "trait/frame_sink.h"
#include "util/log.h"


#######by zengyb


static bool
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {




//声明结构体
myframe yuv_frame;


//初始化长宽
yuv_frame.width= decoder->codec_ctx->width;
yuv_frame.height=decoder->codec_ctx->height;
	//读取数据
	int offest=0;
	for(int i=0;i<yuv_frame.height;i++)
	{
	    memcpy(yuv_frame.data_Y+offest,frame->data[0]+frame->linesize[0]*i,yuv_frame.width);
	    //指向下一行
	    offest += yuv_frame.width;
	}
	 
	offest=0;
	for(int i=0;i<yuv_frame.height/2;i++)
	{
	    memcpy(yuv_frame.data_U+offest,frame->data[1]+frame->linesize[1]*i,yuv_frame.width/2);
	    offest += yuv_frame.width/2;
	}
	 
	offest=0;
	for(int i=0;i<yuv_frame.height/2;i++)
	{
	    memcpy(yuv_frame.data_V+offest,frame->data[2]+frame->linesize[2]*i,yuv_frame.width/2);
	    offest += yuv_frame.width/2;
	}
  //    printf("a");
      
      int aa = zmq_send(response,&yuv_frame,sizeof(yuv_frame),ZMQ_DONTWAIT);
      if (aa == -1) {
    printf("zmq_send error: %s\n", zmq_strerror(errno));
    }

      
   //   printf("%d",aa);


    for (unsigned i = 0; i < decoder->sink_count; ++i) {
        struct sc_frame_sink *sink = decoder->sinks[i];
        if (!sink->ops->push(sink, frame)) {
            LOGE("Could not send frame to sink %d", i);
            return false;
        }
    }
    


    return true;
}

decoder.h-------声明结构体和scoket全局变量
 

#define yuv_size 448*1024

typedef struct{
    int width;
    int height;
    uint8_t data_Y[yuv_size];
    uint8_t data_U[yuv_size/4];
    uint8_t data_V[yuv_size/4];
}myframe;

extern void *response;

 stream.c----初始化socket端口,for循环前初始,最终end的时候删除

###先声明变量
void *response = NULL;
####


 void *context = zmq_ctx_new ();


    response = zmq_socket (context, ZMQ_PUB);
    
    int rc = zmq_bind(response, "tcp://127.0.0.1:5555");
   // printf("%d",rc);
   // printf("111");
    assert (rc == 0);

    for (;;) {
    
        bool ok = stream_recv_packet(stream, packet);
        
        if (!ok) {
            // end of stream
            break;
        }

        ok = stream_push_packet(stream, packet);
        
        av_packet_unref(packet);
        if (!ok) {
            // cannot process packet (error already logged)
            break;
            
          
        }
        

    }

    LOGD("End of frames");

    if (stream->pending) {
        av_packet_free(&stream->pending);
    }

    av_packet_free(&packet);
finally_close_parser:
    av_parser_close(stream->parser);
finally_close_sinks:
    stream_close_sinks(stream);
finally_free_codec_ctx:
    avcodec_free_context(&stream->codec_ctx);
end:
    if (response) {
        zmq_close(response);
    }
    if (context) {
        zmq_ctx_term(context);
    }
    stream->cbs->on_eos(stream, stream->cbs_userdata);

    return 0;
}

至此,源码修改完成,yuv流输出到本地端口,下一步让我们用起来吧!!!

三、订阅与深度学习实时分割

我先给一份实例直接订阅imshow的,如果需要融合到其他深度学习比如检测分割之类的程序基于此进行修改即可

import zmq
import numpy as np
import cv2

# 创建一个ZeroMQ Context
context = zmq.Context()

# 创建一个套接字并连接
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:5555")
socket.setsockopt_string(zmq.SUBSCRIBE, '')

width = 1024# 这里设置你的宽度
height =  448# 这里设置你的高度

while True:
    # 从套接字接收myframe结构体
    yuv_frame = socket.recv()

    # 将bytes数据转化为numpy数组
    y_data = np.frombuffer(yuv_frame[8:8+width*height], dtype=np.uint8).reshape(height, width)
    u_data = np.frombuffer(yuv_frame[8+width*height:8+width*height*5//4], dtype=np.uint8).reshape(height//2, width//2)
    v_data = np.frombuffer(yuv_frame[8+width*height*5//4:8+width*height*3//2], dtype=np.uint8).reshape(height//2, width//2)

    # 这里你可以将YUV格式的图像转化为RGB或者其他你需要的格式,然后进行显示或者处理
    # 例如,使用OpenCV库的cvtColor函数转化为BGR格式
    yuv_image = cv2.merge([y_data, cv2.resize(u_data, (width, height)), cv2.resize(v_data, (width, height))]).astype(np.uint8)
    bgr_image = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2BGR)
    cv2.imwrite("./1.jpg",bgr_image)
    # 实时显示视频
    cv2.imshow('Video', bgr_image)

    # 等待1毫秒,如果此时按下“q”键,那么退出循环
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 销毁所有窗口
cv2.destroyAllWindows()

然后给一份完用unet做分割的代码

    elif mode == "video":
        # 创建一个ZeroMQ Context
        context = zmq.Context()

        # 创建一个套接字并连接
        socket = context.socket(zmq.SUB)
        socket.connect("tcp://127.0.0.1:5555")
        socket.setsockopt_string(zmq.SUBSCRIBE, '')

        width = 1024  # 这里设置你的宽度
        height = 448  # 这里设置你的高度



        frame_counter = 0
        fps = 0.0
        while True:
            t1 = time.time()
            # 从套接字接收myframe结构体
            yuv_frame = socket.recv()
            frame_counter += 1
            if frame_counter % 10 != 0:  # Only process every 10th frame
                continue

            # 将bytes数据转化为numpy数组
            y_data = np.frombuffer(yuv_frame[8:8+width*height], dtype=np.uint8).reshape(height, width)
            u_data = np.frombuffer(yuv_frame[8+width*height:8+width*height*5//4], dtype=np.uint8).reshape(height//2, width//2)
            v_data = np.frombuffer(yuv_frame[8+width*height*5//4:8+width*height*3//2], dtype=np.uint8).reshape(height//2, width//2)

            yuv_image = cv2.merge([y_data, cv2.resize(u_data, (width, height)), cv2.resize(v_data, (width, height))]).astype(np.uint8)
            frame = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2BGR)
            frame1 = frame

            frame = Image.fromarray(np.uint8(frame))
            # # 进行检测
            # t2 = time.time()
            # print(t2-t1)
            
            frame = np.array(unet.detect_image(frame))

            cv2.imshow("video", frame)
             # 等待1毫秒,如果此时按下“q”键,那么退出循环
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            t2 = time.time()
            print(t2-t1)

            
        print("Video Detection Done!")
        # if video_save_path!="":
        #     print("Save processed video to the path :" + video_save_path)
        #     out.release()
        cv2.destroyAllWindows()

记得一定要搁几帧读一下socket,不然推理无法实时,速度太慢,到时候socket里面堵塞太多东西了会巨卡------这里卡太久了,一步步debug才发现的

这里跑的是voc数据集的unet分割,可见才0.3s的延时,凑合凑合就够用了!!! 

error1:

sdl2-config found: NO need '>= 2.0.5'
Run-time dependency sdl2 found: NO (tried pkgconfig and config-tool)

app/meson.build:94:4: ERROR: Dependency "sdl2" not found, tried pkgconfig and config-tool
 

solution:sudo apt-get install libsdl2-dev

error2:ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

solution:sudo apt-get install openjdk-8-jdk

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值