宅科技 - 科技,宅出新生活

搜索
热搜: 活动 交友 discuz
如果你还没有论坛的账号,赶快注册吧!
立即注册

合作站点账号登陆

快捷导航
查看: 9921|回复: 3

[知识点] Android PC投屏简单尝试

[复制链接] [提交至百度]

109

主题

115

帖子

6698

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
6698
发表于 2018-5-8 10:22:20 | 显示全部楼层 |阅读模式
扫码领红包
本帖最后由 maybe 于 2018-5-8 10:34 编辑

代码地址 :https://github.com/deepsadness/MediaProjectionDemo

项目使用说明1.运行Node的Socket服务端
  1. node ./sockt/io-server.js
复制代码


2.运行Node的网页
  1.     node ./sockt/index.js
复制代码


打开 localhost:3000.就可以看到网页了。

  • 运行App。进入 在MainActivity,点击Start按钮,就可以开始了 进入Activity时,已经回去连接socket
注意:
  • 需要在局域网内运行。App内需要配置好Socket链接的ip. 在SocketIoManager内
  • 具体的内容,还是请看一下代码。

代码地址 :https://github.com/deepsadness/MediaProjectionDemo

###效果预览

###简单说明:

  • 使用Android MediaProjection Api来完成视频的截图
  • 通过WebSocket进行链接。将图片传递给网页
想法来源

看到vysor,觉得特别好玩,于是就想着自己能不能试着做一个类似的功能出来。搜索了相关实现。发现网上已经有网友针对vysor做了分析。于是就照着思路,按图索骥,当作对MediaProjection Api的练习,来完成这个小项目

主要思路

####1. 获取屏幕的截屏

  • 创建VirtualDisplay Android在Api 21以上为我们已经提供了系统的Api可以进行操作。 主要是这几个类的相互配合MediaProjection和VirtualSurface,还有截图的话,使用ImageReader,三个类配合使用。

  1. public RxScreenShot createImageReader() {
  2.         //注意这里使用RGB565报错提示,只能使用RGBA_8888
  3.         mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1000);
  4.         mSurfaceFactory = new ImageReaderSurface(mImageReader);
  5.         createProject();
  6.         return this;
  7.     }

  8.     private void createProject() {
  9.         mediaProjection.registerCallback(mMediaCallBack, mCallBackHandler);
  10.         //通过这种方式来创建这个VirtualDisplay,并将数据传递给ImageReader提供surface
  11.         mediaProjection.createVirtualDisplay(TAG + "-display", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
  12.                 mSurfaceFactory.getInputSurface(), null, null);
  13.     }
复制代码

  • 获取屏幕截图 可以通过ImageReader类。配套Image来获奖获得的数据转成Bitmap
  1. /*
  2. 封装成了Observable对象。
  3. */
  4. public class ImageReaderAvailableObservable extends Observable<ImageReader> {

  5.     public static ImageReaderAvailableObservable of(ImageReader imageReader) {
  6.         return new ImageReaderAvailableObservable(imageReader, null);

  7.     }

  8.     public static ImageReaderAvailableObservable of(ImageReader imageReader,Handler handler) {
  9.         return new ImageReaderAvailableObservable(imageReader, handler);
  10.     }

  11.     private final ImageReader imageReader;
  12.     private final Handler handler;

  13.     private ImageReaderAvailableObservable(ImageReader imageReader, Handler handler) {
  14.         this.imageReader = imageReader;
  15.         this.handler = handler;
  16.     }

  17.     @Override
  18.     protected void subscribeActual(Observer<? super ImageReader> observer) {
  19.         Listener listener = new Listener(observer, imageReader);
  20.         observer.onSubscribe(listener);
  21.         //设置准备好的监听事件
  22.         imageReader.setOnImageAvailableListener(listener, handler);
  23.     }


  24.     static class Listener implements Disposable, ImageReader.OnImageAvailableListener {
  25.         private final AtomicBoolean unsubscribed = new AtomicBoolean();
  26.         private final ImageReader mImageReader;
  27.         private final Observer<? super ImageReader> observer;

  28.         Listener(Observer<? super ImageReader> observer, ImageReader imageReader) {
  29.             this.mImageReader = imageReader;
  30.             this.observer = observer;
  31.         }


  32.         @Override
  33.         public void onImageAvailable(ImageReader reader) {
  34.             if (!isDisposed()) {
  35.               //将准备好的reader发送出去,进行处理
  36.                 observer.onNext(reader);
  37.               //注意:这里如果不调用onCompleted事件。其实这个监听会不断回调事件
  38. //                observer.onComplete();
  39.             }
  40.         }

  41.         @Override
  42.         public void dispose() {
  43.             if (unsubscribed.compareAndSet(false, true)) {
  44.                 mImageReader.setOnImageAvailableListener(null, null);
  45.             }
  46.         }

  47.         @Override
  48.         public boolean isDisposed() {
  49.             return unsubscribed.get();
  50.         }
  51.     }
  52. }

  53. /*
  54. 调用开始截屏的方法
  55. */
  56. public Observable<Object> startCapture() {
  57.         return ImageReaderAvailableObservable.of(mImageReader)
  58.                 .map(imageReader -> {
  59.                     String mImageName = System.currentTimeMillis() + ".png";
  60.                     Log.e(TAG, "image name is : " + mImageName);
  61.                     Bitmap bitmap = null;
  62.                     //从imageReader中获取到最新的Image
  63.                     Image image = imageReader.acquireLatestImage();
  64.                     if (image == null) {

  65.                     } else {
  66.                         //将Image对象转成bitmap
  67.                         int width = image.getWidth();
  68.                         int height = image.getHeight();
  69.                         //byteBuffer都保存在image.Plane中
  70.                         final Image.Plane[] planes = image.getPlanes();
  71.                         final ByteBuffer buffer = planes[0].getBuffer();
  72.                         int pixelStride = planes[0].getPixelStride();
  73.                         int rowStride = planes[0].getRowStride();
  74.                         int rowPadding = rowStride - pixelStride * width;
  75.                         bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
  76.                         bitmap.copyPixelsFromBuffer(buffer);
  77.                         bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
  78.                         //这里使用完要记得close.如果没有close,当imageReader达到max_count上限时将会抛出异常
  79.                         image.close();
  80.                     }
  81.                     return bitmap == null ? new Object() : bitmap;
  82.                 });
  83.     }
复制代码

这里需要注意的是,需要通过这个回调,每当屏幕发生变化,就会回调这个接口,可以得到最新的截图。ImageReader::setOnImageAvailableListener

2. 搭建Socket连接,将图片的数据进行传递

因为我们的目标是在网页内打开,所以需要和网页进行通信。 可以简单的使用WebSocket进行双方通向

通过Socket.iohttps://socket.io/ 就可以简单的实现

  • Android端的代码 通过WebSocket将Bitmap的字节码发送出去
  1.     private fun sendBitmap(it: Bitmap) {
  2.         val byteArrayOutputStream = ByteArrayOutputStream()
  3.         it.compress(Bitmap.CompressFormat.JPEG, 60, byteArrayOutputStream)
  4.         val byteArray = byteArrayOutputStream.toByteArray()
  5.         SocketIoManager.getInstance().send(byteArray)
  6.     }

  7.     public void send(byte[] bitmapArray) {
  8.         if (!mSocketReady) {
  9.             return;
  10.         }
  11.         if (bitmapArray != null) {
  12.             mSocket.emit("event", bitmapArray);
  13.         }
  14.     }
复制代码

  • Node端的代码 简单的SocketIo实现.代码在 /sockt/io-server.js
  1. var io = require('socket.io')();
  2. var clients = []
  3. io.on('connection', function (client) {
  4.     clients.push(client);
  5.     console.log('connection!');

  6.     client.emit('join', 'welcome to join!!')

  7.     client.on('chat message', function (msg) {
  8.         console.log("receive msg=" + msg);

  9.     });
  10.     client.on('event', function (msg) {
  11.         // console.log("event", msg);
  12.         console.log("event", "send image~~");
  13.         //通过event事件出去
  14.         clients.forEach(function (it) {
  15.             it.emit('event', msg)
  16.         })
  17.     });
  18. });
  19. io.on('disconnect', function (client) {

  20. })
  21. io.listen(9000);
复制代码

3. 如何将图片显示出来

代码在 /sockt/index.html中 html中的src就可以直接对传递byte[]的进行解析。

  1. socket.on('image', function (msg) {
  2.       var arrayBufferView = new Uint8Array(msg);
  3.       var blob = new Blob([arrayBufferView], { type: "image/jpeg" });
  4.       var urlCreator = window.URL || window.webkitURL;
  5.       var imageUrl = urlCreator.createObjectURL(blob);
  6.       var img = document.getElementById("screen");
  7.       // var img = document.querySelector("#photo");
  8.       img.src = imageUrl;
复制代码

4. 下一步

下一步,就是使用 录制的Api,来做录屏直播了。






上一篇:Android PC投屏功能实现的示例代码
下一篇:手机+PC双屏显示:android端即时预览PC端修改的代码
回复

使用道具 举报

0

主题

17

帖子

517

积分

顶级大神

Rank: 4

积分
517
发表于 2018-8-18 15:43:38 | 显示全部楼层
感觉好高级的样子
回复 支持 反对

使用道具 举报

0

主题

19

帖子

21

积分

吃瓜群众

Rank: 1

积分
21
发表于 2018-11-29 16:46:54 | 显示全部楼层
666666666666666
回复 支持 反对

使用道具 举报

0

主题

99

帖子

99

积分

提鞋小弟

Rank: 2

积分
99
发表于 2024-4-28 01:13:27 | 显示全部楼层

666666666666666666
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

抖音账号
关注抖音
查看在线教程,私信咨询


手机版|小黑屋|网站地图|宅科技 ( 粤ICP备15107403号

GMT+8, 2024-10-18 16:52 , Processed in 0.143079 second(s), 27 queries .

Copyright © 2016 宅科技 | 智能终端极客社区

Powered by Discuz! X3.4 Licensed

快速回复 返回顶部 返回列表