前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android无线传屏功能实现

Android无线传屏功能实现

作者头像
码客说
发布于 2020-06-15 02:34:41
发布于 2020-06-15 02:34:41
1.3K00
代码可运行
举报
文章被收录于专栏:码客码客
运行总次数:0
代码可运行

前言

通过Websocket进行图片流传输来实现

现在要实现Android采集屏幕通过Websocket在另一个Android设备上显示

那么我们就要采集屏幕=>生成二进制=>ws传输=>ws接收=>二进制转图片=>播放图片

本地测试

在接入websocket之前 我们现在本地实现采集屏幕=>生成二进制=>二进制转图片=>播放图片这样的流程

图片工具类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.view.View;

import java.io.ByteArrayOutputStream;

public class ZJViewUtil {

    private static ZJViewUtil viewUtil = null;

    public static ZJViewUtil instance() {
        if (viewUtil == null) {
            viewUtil = new ZJViewUtil();
        }
        return viewUtil;
    }

    /**
     * 获取视图的Bitmap
     *
     * @param v
     * @return
     */
    public Bitmap loadBitmapFromViewBySystem(View v) {
        if (v == null) {
            return null;
        }
        v.setDrawingCacheEnabled(true);
        v.buildDrawingCache();
        Bitmap bitmap = v.getDrawingCache();
        return bitmap;
    }


    /**
     * Bitmap转字节
     *
     * @param bitmap
     * @return
     */
    public byte[] bitmap2byte(Bitmap bitmap) {
        if (bitmap != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
            return baos.toByteArray();
        } else {
            return new byte[0];
        }
    }

    /**
     * 字节转Bitmap
     *
     * @param b
     * @return
     */
    public Bitmap byte2bitmap(byte[] b) {
        Bitmap bitmap = BitmapFactory.decodeByteArray(
                b,
                0,
                b.length
        );

        return bitmap;
    }

    /**
     * Bitmap缩放
     *
     * @param bitmap
     * @param width
     * @param height
     * @return
     */
    public static Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        Matrix matrix = new Matrix();
        float scaleWidth = ((float) width / w);
        float scaleHeight = ((float) height / h);
        matrix.postScale(scaleWidth, scaleHeight);
        Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
        return newbmp;
    }
}

先看下BitmapFactory.Options里我们使用的主要属性

  • inBitmap:如果该值不等于空,则在解码时重新使用这个Bitmap。
  • inMutable:Bitmap是否可变的,如果设置了inBitmap,该值必须为true
  • inPreferredConfig:指定解码颜色格式。
  • inJustDecodeBounds:如果设置为true,将不会将图片加载到内存中,但是可以获得宽高。
  • inSampleSize:图片缩放的倍数,如果设置为2代表加载到内存中的图片大小为原来的2分之一,这个值总是和inJustDecodeBounds配合来加载大图片,在这里我直接设置为1,这样做实际上是有问题的,如果图片过大很容易发生OOM。

注意

我们在用BitmapFactory生成图片的时候如果不设置的option的话,每次都会生成新的Bitmap对象,频繁的生成释放会导致内存抖动,所以可以用inBitmap来防止,我这里暂时还没用,如果使用的话,我们可以定义一个图片池,循环利用其中的对象,但是一定要保证正在展示的对象不能被同时被修改,会导致显示有横线。 图片的编码格式用png和webp都可以,不知道为啥jpeg不行,很是奇怪!

图片播放器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import android.view.TextureView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;


public class PicturePlayerView extends TextureView implements TextureView.SurfaceTextureListener {

    private Paint mPaint;//画笔
    private Rect mSrcRect;
    private Rect mDstRect;

    private boolean available = false;

    private List<Bitmap> mReusableBitmaps = Collections.synchronizedList(new ArrayList<Bitmap>());

    List<Disposable> dplist = new ArrayList<>();

    public PicturePlayerView(Context context) {
        super(context);
        init();
    }

    public PicturePlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PicturePlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOpaque(false);//设置背景透明,记住这里是[是否不透明]
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);//创建画笔
        mSrcRect = new Rect();
        mDstRect = new Rect();
        this.setSurfaceTextureListener(this);
    }

    private void beginRender() {
        Disposable dp = Observable.interval(0, 25, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        if (mReusableBitmaps.size() > 0) {
                            Bitmap bitmap = mReusableBitmaps.remove(0);
                            drawBitmap(bitmap);
                        }
                    }
                });
        dplist.add(dp);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        available = true;
        this.beginRender();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        for (Disposable dp : dplist) {
            dp.dispose();
        }
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }

    public void addBitmap(Bitmap bitmap) {
        if (available) {
            if (bitmap != null) {
                if (mReusableBitmaps.size() > 3) {
                    Bitmap bt = mReusableBitmaps.remove(0);
                    recycleBitmap(bt);
                }
                mReusableBitmaps.add(bitmap);
            }
        }
    }


    private void drawBitmap(Bitmap bitmap) {
        Canvas canvas = lockCanvas();//锁定画布
        if (canvas != null) {
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);// 清空画布
            mSrcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
            mDstRect.set(0, 0, getWidth(), bitmap.getHeight() * getWidth() / bitmap.getWidth());
            canvas.drawBitmap(bitmap, mSrcRect, mDstRect, mPaint);//将bitmap画到画布上
            unlockCanvasAndPost(canvas);//解锁画布同时提交
            recycleBitmap(bitmap);
        }
    }


    private static void recycleBitmap(Bitmap bitmap) {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
    }
}

注意

图片播放是可以用ImageView来直接加载,但是问题是如果接收到的图片的间隔不一致的时候会感觉明显的卡顿,所以用自定义TextureView来处理,里面缓存要保存的图片,以每秒25帧播放,但是如果图片的产生速度较快的话,会导致缓存的图片越来越多,从而oom了,所以我在缓存中至多保留最新的三个,其它的丢弃

Activity

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private fun initTask() {
    Observable.interval(0, 100, TimeUnit.MILLISECONDS)
        .compose(this.bindToLifecycle())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {
            if (shownum < 10000) {
                shownum += 1
            } else {
                shownum = 0
            }
            left_textview.text = "${shownum}"
            renderImage()
        }.isDisposed
}

private fun renderImage() {
    val image = ZJViewUtil.instance().loadBitmapFromViewBySystem(leftview)
    if (image != null) {
        val imgnew = image.copy(Bitmap.Config.ARGB_8888, true)
        //本地渲染
        doAsync {
            val bts = ZJViewUtil.instance().bitmap2byte(imgnew)
            val bitmap = ZJViewUtil.instance().byte2bitmap(bts)
            runOnUiThread {
                picPlayerView.addBitmap(bitmap)
            }
        }
    }
}

注意

image.copy(Bitmap.Config.ARGB_8888, true) 这句代码非常关键 在我们采集view的图片的时候,获取到的Bitmap对象的指针是不变的,如果不做copy,那么在我们异步转换二进制的时候就会中途Bitmap对象被修改,导致图片中会产生横线。

通过WS传输

考虑到以后二进制传输其它类型的数据,所以我这里定义了数据的格式

数据头+JSON数据+传输数据

  • 数据头用来保存JSON数据的长度,方便截取JSON
  • JSON数据中保存要传输的参数
  • 传输数据才是真正要传输的二进制数据

数据传输

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var msg = JSON.toJSONString(obj)
var bodyByteArr = msg.toByteArray(Charsets.UTF_8)
var bytelengthStr = "" + bodyByteArr.size
while (bytelengthStr.length < 6) {
  bytelengthStr = "0" + bytelengthStr
}

var headByteArr = bytelengthStr.toByteArray(Charsets.UTF_8)

var data_arr = ByteArray(headByteArr.size + bodyByteArr.size + data.size)

System.arraycopy(headByteArr, 0, data_arr, 0, headByteArr.size)
System.arraycopy(bodyByteArr, 0, data_arr, headByteArr.size, bodyByteArr.size)
System.arraycopy(data, 0, data_arr, headByteArr.size + bodyByteArr.size, data.size)

数据接收

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
val dataHead = ByteArray(6)
System.arraycopy(dataAll, 0, dataHead, 0, 6)
val headStr = String(dataHead, Charsets.UTF_8)
L.e("headStr:${headStr}")
val jsonSize = headStr.toInt()
val dataJson = ByteArray(jsonSize)
System.arraycopy(dataAll, 6, dataJson, 0, jsonSize)
val jsonStr = String(dataJson, Charsets.UTF_8)
L.e("jsonStr:${jsonStr}")
val dataImageSize = dataAll.size - 6 - jsonSize
val dataImg = ByteArray(dataImageSize)
System.arraycopy(dataAll, 6 + jsonSize, dataImg, 0, dataImageSize)

方法介绍

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 代码解释:

  • Object src : 原数组
  • int srcPos : 原数组的起始位置
  • Object dest : 目标数组
  • int destPos : 目标数组的起始位置
  • int length : 要Copy的数组的长度
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
go语言环境安装
软件的安装常用三种方式 1. 在线安装 这种安装方式是通过系统提供的安装工具,自动下载安装包,并自动安装到系统的固定目录下 2. 离线二进制安装 通过手动下载官方提供编译好的二进制执行程序压缩包,在本地手动的解压到指定的目录下,并将可执行程序的路径加入到环境变量PATH中,使得在命令行工具中,可以在任意目录中使用该命令行工具。 3. 离线源码安装 在本地对源码进行make操作,将其编译为二进制的可执行程序。并将其copy到执行的目录下并加入到PATH环境变量中
暮雨
2019/08/21
1K0
go语言环境安装
GoLang的安装和使用
Go 语言安装包下载地址:https://golang.google.cn/dl/
代码的路
2022/08/23
5090
GoLang的安装和使用
Go学习之新奇视角 - 入门篇(一)
最近工作中,需要研究Go语言的区块链项目,作为一个Java出身的程序汪,不得不开始走向新的火热学习当中去。
23号杂货铺
2019/09/27
6510
Go学习之新奇视角 - 入门篇(一)
【2023最新版】Linux (WSL:Ubuntu22.04)安装Go1.20.6+Win11:安装Go1.20.6+GoLand2023.1.4+配置环境
All releases - The Go Programming Language (google.cn)
Qomolangma
2024/07/29
2780
【2023最新版】Linux (WSL:Ubuntu22.04)安装Go1.20.6+Win11:安装Go1.20.6+GoLand2023.1.4+配置环境
Golang 入门系列(一)Go环境搭建
安装go 的时候,安装程序会自动把相关目录写到系统环境。但是如果是zip 的安装,需要自己手动添加。
章为忠学架构
2018/08/17
2.1K0
第7节 Go语言环境搭建
当然你也可以登录Golang的国内网站:https://golang.google.cn/
小尘哥
2019/05/28
1.2K0
1、go环境的安装卸载及环境变量配置
背景: go环境的安装和卸载, 有时已经安装过,需要对go版本进行升级. 所以我们需要先卸载, 然后在安装
用户7798898
2022/05/09
2.9K0
1、go环境的安装卸载及环境变量配置
mac go环境的安装和卸载
背景: go环境的安装和卸载, 之前安装过go1.12, 现在项目需要,要安装go1.13. 所以要做的是先卸载, 然后在安装
用户7798898
2020/09/27
9.6K0
mac go环境的安装和卸载
[ Golang ] Golang 入门教程 ( 一 ) 运行环境搭建
安装软件包可以上 go中文网上下载,选择推荐下载版本即可,一般推荐的都是稳定版本。
GavinUI
2021/05/03
1.3K0
[ Golang ] Golang 入门教程 ( 一 ) 运行环境搭建
Go基础系列 | 2. 环境搭建
选择我上面提供的安装包是一种 Linux 下通用的方式。那还有其它更简单的方式吗?回答:是有的。
潇洒哥和黑大帅
2021/06/01
7440
Go基础系列 | 2. 环境搭建
Go安装以及目录结构和环境变量
下载地址 https://studygolang.com/dl 选择自己对应的操作系统
阿伟
2019/07/08
1.3K0
Go安装以及目录结构和环境变量
Golang开发环境搭建(Windows)[通俗易懂]
1. 下载对应安装包安装, golang下载地址: Downloads – The Go Programming Language
全栈程序员站长
2022/11/10
4.8K0
Golang开发环境搭建(Windows)[通俗易懂]
Windows搭建Go开发环境GoLand
首先先来安装一下Go语言的SDK,目前Go语言的最新版本为Go 1.8.3 。Go下载页面列出了各种操作系统的安装包。如果选择Windows MSI安装包的话,会将Go安装到C:\Go。如果不希望将Go安装到C盘,就需要自己下载Windows版本的ZIP文件,手动安装并设置环境变量。我这里就是这么做的。
孙小北
2024/01/30
3900
Go语言入门——环境准备篇(一)
Robert Griesemer:曾协助制作Java的HotSpot编译器和Chrome浏览器的JavaScript引擎V8
arcticfox
2019/07/31
6510
Go环境搭建-从安装到Hello World
官网:https://golang.org/dl/ 谷歌镜像:https://golang.google.cn/dl/ Go语言中文网:https://studygolang.com/dl
唔仄lo咚锵
2022/05/11
3820
Go环境搭建-从安装到Hello World
第一章、Go安装与Goland破解
一直点下一步安装即可,默认是安装在“C:\Go”,如果自己换成其它目录则需添加环境变量。
zhang_derek
2019/08/01
4.1K1
第一章、Go安装与Goland破解
Golang集成开发环境搭建
最近在学习Go语言,为consul指定模块的开发做准备,今天搭建了一下go语言的环境,这里我把Go语言的环境搭建部分总结一下。文章主要分为两个部分,第一个部分是Golang语言包的下载和安装,第二部分是Jetrain公司的集成开发环境GoLand的搭建。
AsiaYe
2019/11/06
3.1K0
Golang集成开发环境搭建
Go安装以及开发环境搭建
如果你下载的是msi类型的安装包,那么直接安装即可,会自动帮你设置好环境变量。如果是zip类型的压缩包,那么需要设置两个环境变量,以压缩包解压到F盘根目录为例:
晓晨
2019/08/29
8680
Go安装以及开发环境搭建
Python 与 Go 混合开发 | 多平台下 Go 语言开发环境配置
Windows 平台和 Mac 平台推荐下载可执行文件版,Linux平台下载压缩文件版。
咸鱼学Python
2020/11/20
3.3K0
Python 与 Go 混合开发 | 多平台下 Go 语言开发环境配置
Go语言及Beego框架环境搭建
1、根据操作系统是32位或64位选择对应的go1.8.3.windows-XXX.msi文件,双击开始安装,一路下一步,即可完成安装。安装到选择目标文件夹时,可以选D盘。
我的小碗汤
2018/08/22
1.3K0
Go语言及Beego框架环境搭建
相关推荐
go语言环境安装
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验