Android屏幕共享-硬编码硬解码

Android屏幕共享-硬编码硬解码

说起Android之间的屏幕共享,第一次接触会比较陌生,不过大家多少有了解过ffmpeg,看上去是不是很熟悉?ffmpeg是一套处理音视频的开源程序,但对于C了解较少的同学,编译起来很复杂。

有同学问有没有纯JAVA操作的方法呢,还真有。

一、效果图

Demo界面

二、软解码和硬解码

  • 软解码

利用CPU的计算进行解码,比如使用FFmpeg解码,由于解码是通过CPU运算,所以加大CPU负担,增加耗电。

  • 硬解码

利用手机自带处理视频的芯片专门模块编码进行解码,如 dsp。对CPU要求比较低,主要依赖于硬件,所以解码芯片在不同的手机上,表现可能会有不一致的情况。好处是硬解由于是单独的处理芯片,所以速度比软解码要快。

三、代码分析

3.1 Android 硬编码

硬编码主要是使用MediaCodec访问底层的codec来实现编解码,它是Android提供的用于对音视频进行编解码的类。

整体来说步骤分为以下步骤:

详细点的代码如下:

  1. 申请录屏权限

private void requestCapturePermission() throws Exception {if ((Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP)) {//5.0 之后才允许使用屏幕截图mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(),REQUEST_MEDIA_PROJECTION);} else {throw new Exception("android版本低于5.0");}}

2.在确认的回调中得到MediaProjection

MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
  1. 配置并获取MediaCodec

private MediaCodec prepareVideoEncoder() throws IOException {MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mVideoEncodeConfig.width, mVideoEncodeConfig.height);format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);// format.setInteger(KEY_BIT_RATE, (int) (mVideoEncodeConfig.width * mVideoEncodeConfig.height * mVideoEncodeConfig.rate * mVideoEncodeConfig.factor));format.setInteger(KEY_BIT_RATE, (int) (mVideoEncodeConfig.width * mVideoEncodeConfig.height * mVideoEncodeConfig.rate * mVideoEncodeConfig.factor));format.setInteger(KEY_FRAME_RATE, mVideoEncodeConfig.rate); //帧format.setInteger(KEY_I_FRAME_INTERVAL, mVideoEncodeConfig.i_frame);// 该代码能够达到很强的清晰度,但是在华为nova 5i 10。0上不支持。参考:https://www.jianshu.com/p/a0873b4a92b6// format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);// -----------------ADD BY XU.WANG 当画面静止时,重复最后一帧--------------------------------------------------------format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 45);//------------------MODIFY BY XU.WANG 为解决MIUI9.5花屏而增加...-------------------------------if (Build.MANUFACTURER.equalsIgnoreCase("XIAOMI")) {format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);} else {format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);}format.setInteger(MediaFormat.KEY_COMPLEXITY, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);MediaCodec mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);Surface surface = mediaCodec.createInputSurface();mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display", mVideoEncodeConfig.width, mVideoEncodeConfig.height, 1,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);return mediaCodec;}

4.启动MediaCodeC

mMediaCodec.start();

5.开启线程,不断的编码


mVideoEncodeThread = new Thread(new Runnable() {@Overridepublic void run() {while (mVideoCoding && !Thread.interrupted()) {try {ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();int outputBufferId = mMediaCodec.dequeueOutputBuffer(vBufferInfo, 0);if (outputBufferId >= 0) {ByteBuffer bb = outputBuffers[outputBufferId];onEncodedAvcFrame(bb, vBufferInfo);mMediaCodec.releaseOutputBuffer(outputBufferId, false);}} catch (Exception e) {e.printStackTrace();break;}}}});

6.在onEncodedAvcFrame处理编码数据

7.传输数据

3.2 Android 硬解码

整体来说步骤分为以下步骤:

详细代码步骤如下:

  1. 接收数据

将接收到的二进制数据流放进解码的工具类


@Overridepublic void onReceive(byte[] packet) {mediaDecodeUtil.decodeFrame(packet);}

2.创建SurfaceView

在SurfaceView创建后和解码类关联


surface_view.getHolder().addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.d(TAG, "surfaceCreated");try {if (mediaDecodeUtil != null)mediaDecodeUtil.onInit(surface_view);} catch (IOException e) {e.printStackTrace();}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.d(TAG, "surfaceChanged");}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.d(TAG, "surfaceDestroyed");if (mediaDecodeUtil != null)mediaDecodeUtil.onDestroy();}});

private void configDecoder(MediaFormat newMediaFormat, SurfaceView surfaceView) {if (mediaCodec == null) return;// 在SurfaceView加载完成前,调用以下方法会报错,此处TryCatch用以应付在OnCreate中执行初始化导致的崩溃try {mediaCodec.stop();mediaFormat = newMediaFormat;// MediaCodec配置对应的SurfaceView// !!!注意,这行代码需要SurfaceView界面绘制完成之后才可以调用!!!mediaCodec.configure(newMediaFormat, surfaceView.getHolder().getSurface(), null, 0);// 解码模式设置// mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) // 表示编码器会尽量把输出码率控制为设定值mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ); // 示完全不控制码率,尽最大可能保证图像质量// mediaFormat.setInteger( MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) // 表示编码器会根据图像内容的复杂度(实际上是帧间变化量的大小)来动态调整输出码率,图像复杂则码率高,图像简单则码率低mediaCodec.start();// 设置视频保持纵横比,此方法必须在configure和start之后执行才有效mediaCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);} catch (Exception e) {e.printStackTrace();}}

3.芯片解码

这部分的逻辑是这样的,每当接收到数据时,就去寻找有没有空闲的DSP解码芯片,有的话,就去处理,没有的话就设置了短暂时间循环去寻找空闲的解码芯片,因为只有当有空闲芯片时,才可以进行解码,不然会出现绿屏花屏现象。


private void decodeFrameDetail(byte[] bytes) {// 找出dsp芯片可用区域的索引,如果有可用,则返回索引,如果没有,则返回 -1int inIndex = mediaCodec.dequeueInputBuffer(TIME_OUT_US);if (inIndex >= 0) {// 取出对应索引的可用区域ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inIndex);if (byteBuffer != null) {// 把一帧的数据放入可用区域,byteBuffer.put(bytes, 0, bytes.length);mediaCodec.queueInputBuffer(inIndex, 0, bytes.length, 0, 0);// mediaCodec.queueInputBuffer(inIndex, 0, bytes.length, System.currentTimeMillis(), 0);}} else {// 如果没有可用的dsp,考虑用个for循环,循环5-10次查找可用的dsp。还不行就让他花屏把。Log.d(TAG, "目前没有可用的dsp");return;}// 取出编码好的数据MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();if (!isNeedContinuePlay) return;int outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIME_OUT_US);// 编码好的全部取出来。while (outIndex >= 0) {mediaCodec.releaseOutputBuffer(outIndex, true);outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIME_OUT_US);if (mIsNeedFixWH) {fixHW();mIsNeedFixWH = false;}}}

3.3 传输

由于是编码好的字节流会发送频繁、并且数据量比较大,所以Android 原生的Socket使用TCP的会很麻烦,比如还要考虑分包粘包的场景,虽然这些问题可以解决,但为了方便起见,还是使用WebSocket,因为WebSocket的协议是基于包,而TCP基于流,既然WebSocket协议都已经帮忙做好了,那么Demo上就先用WebSocket。

  1. 首先需要引入依赖,因为不属于原生的范畴
implementation "org.java-websocket:Java-WebSocket:1.3.6"

2.封装两个工具类,一个客户端,一个服务端,使得我们拿到编码好的数据就可以放工具类中放。

客户端重点代码


public class MWebSocketClient extends WebSocketClient {private final String TAG = "MWebSocketClient";private boolean mIsConnected = false;private CallBack mCallBack;public MWebSocketClient(URI serverUri, CallBack callBack) {super(serverUri);this.mCallBack = callBack;}@Overridepublic void onOpen(ServerHandshake handshakedata) {// ...}@Overridepublic void onMessage(String message) {// ...}@Overridepublic void onMessage(ByteBuffer bytes) {byte[] buf = new byte[bytes.remaining()];bytes.get(buf);if (mCallBack != null)mCallBack.onClientReceive(buf);}@Overridepublic void onClose(int code, String reason, boolean remote) {// ...}@Overridepublic void onError(Exception ex) {// ...}}

服务端重点代码


public class MWebSocketServer extends WebSocketServer {@Overridepublic void onOpen(WebSocket webSocket, ClientHandshake handshake) {}@Overridepublic void onClose(WebSocket conn, int code, String reason, boolean remote) {}@Overridepublic void onMessage(WebSocket conn, String message) {}@Overridepublic void onError(WebSocket conn, Exception ex) {}@Overridepublic void onStart() {}}

粉丝福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/729431.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二维码门楼牌管理系统应用场景:社会服务与福利的智能化革新

文章目录 前言一、二维码门楼牌管理系统的基本功能二、在社会服务领域的应用三、在福利保障领域的应用四、结论 前言 在数字化浪潮的推动下,二维码门楼牌管理系统以其便捷、高效的特点,正逐渐渗透到社会服务和福利的各个领域。这一系统不仅为市民提供了…

Claude3 目前最强大模型

过内试用连接 ChatAI (whjin.cn)http://chatyy.whjin.cn/?userid51913030&typebaidu&keywordid0&bd_vid12153741466503666331 Claude 3: A new generation of AI The Claude 3 family of models represents the cutting edge of AI technology, offering unpara…

RN开发搬砖经验之-如何处理FlashList组件加载后调用scrollToIndex没有滚动指定位置

前言 如题,这里只能说是处理,起正向作用的临时方案,因为我也着实没搞懂这个BUG的具体原因,看github上有提相关的issuesFor long lists with different item types scrollToIndex does not work reliable,但看官方没有…

Extended Feature Pyramid Network for SmallObject Detection

摘要 各种尺度的特征耦合会削弱小对象的性能,本文中,我们提出了具有超高分辨率金字塔的扩展特征金字塔网络(EFPN ),专门用于小目标检测。具体来说,我们设计了一个新模块,称为特征纹理转移&#…

【MySQL 系列】MySQL 起步篇

MySQL 是一个开放源代码的、免费的关系型数据库管理系统。在 Web 开发领域,MySQL 是最流行、使用最广泛的关系数据库。MySql 分为社区版和商业版,社区版完全免费,并且几乎能满足全部的使用场景。由于 MySQL 是开源的,我们还可以根…

统信os平台普通用户无法调用clockdiff的问题解决方法

问题描述 部署OceanBase 时遇到clockdiff: socket: operation not permitted 问题,从官网了解到可以通过setcap cap_net_rawep "$(which clockdiff)"解决 在centos平台,这个可以解决问题,但是在统信os平台失败了。。 参考: Ocea…

怎么把一个视频分割成几个?技巧在这里

在视频编辑的过程中,有时我们需要将一个较长的视频细分成多个部分,以便更灵活地进行处理、调整或分享。这个过程可以帮助我们有效管理视频内容,提取关键片段,并且适应不同的发布需求。在本文中,我们将深入探讨怎么把一…

JavaScript改变this指向的三种方法

在JavaScript中,可以使用call()、apply()或bind()来改变函数的this指向。 1.call(): 通过调用函数并传入新的上下文对象作为参数,将函数内部的this关键字指向该对象。示例代码如下所示 function greet(name) {console.log("Hello " name); …

机器学习笔记 DeepFakes和换脸技术简述

一、简述 人脸检测一直是 2000 年代初的主要研究课题。差不多二十年后,这个问题基本上得到了解决,并且人脸检测在大多数编程语言中都可以作为库使用。甚至换脸技术也不是什么新鲜事,并且已经存在了好些年了。 早在2016年左右就有基于OpenCV进行面部交换的方式了,主要是基于…

第107讲:Mycat实践指南:取模分片下的水平分表详解

文章目录 1.使用取模分片水平分表2.水平分表取模分片案例2.1.准备测试的表结构2.2.配置Mycat实现范围分片的水平分表2.2.1.配置Schema配置文件2.2.2.配置Rule分片规则配置文件2.2.3.配置Server配置文件2.2.4.重启Mycat 2.3.写入数据观察水平分表效果 1.使用取模分片水平分表 平…

SVG 渐变边框在 CSS 中的应用

SVG 渐变边框在 CSS 中的应用 <template><div class"home"><div class"one"><svg width"100%" height"100%"><rect x"2" y"2" width"100%" height"100%" fill&q…

【扩散模型系列1】扩散模型背景|DDPMs|LDM

目录 概述DPMDDPM前向加噪反向去噪UNet 训练阶段推理阶段 IDDPMClassifier GuidanceGLIDELDM模型结构VAE扩散模型U-Net 训练阶段推理阶段 参考资料 概述 扩散模型&#xff1a;和其他生成模型一样&#xff0c;实现从噪声&#xff08;采样自简单的分布&#xff09;生成目标数据样…

鸿蒙开发(七)添加常用控件(上)

相信大家已经对鸿蒙开发的布局有了基本的了解。之前我们提到过&#xff0c;一个好的UI&#xff0c;离不开选择合理的布局。当然&#xff0c;也离不开适当的控件。本篇文章&#xff0c;带着大家一起学习下如何在页面里面添加常用的控件。由于控件较多&#xff0c;我会分为两篇文…

Windows®、Linux® 和 UNIX® 系统都适用的远程桌面工具 OpenText ETX

Windows、Linux 和 UNIX 系统都适用的远程桌面工具 OpenText ETX 为 Windows、Linux 和 UNIX 实施精益、经济高效的虚拟化&#xff1b;提供完整的远程 Windows 可用性&#xff1b;以类似本地的性能远程工作&#xff1b;安全地保护系统和知识产权&#xff08;IP&#xff09;&am…

关于 Runes 协议及「公开铭刻」发行机制的拓展讨论

撰文&#xff1a;MiX 编辑&#xff1a;Faust&#xff0c;极客 web3 2024 年 3 月 2 日&#xff0c;Runes 生态基础设施项目 Rune alpha 的创始人&#xff0c;在 Github 的公开议题中&#xff0c;与 Runes 协议创始人 Casey 展开了讨论&#xff0c;双方对如何拓展 Runes 协议的…

智慧灯杆-智慧城市照明现状分析(1)

城市道路照明是城市公共设施的重要组成部分,而随着城镇化建设的推进,城市道路照明路灯的数量越来越多,能耗越来越高,供电趋于紧张。此外,城市照明的维护工作和高昂的维护成本(人工控制、路灯巡查等),给城市管理造成了巨大的困难。管理部门需要更有效率的管理和节能方案…

支持向量机 SVM | 线性可分:软间隔模型

目录 一. 软间隔模型1. 松弛因子的解释小节 2. SVM软间隔模型总结 线性可分SVM中&#xff0c;若想找到分类的超平面&#xff0c;数据必须是线性可分的&#xff1b;但在实际情况中&#xff0c;线性数据集存在少量的异常点&#xff0c;导致SVM无法对数据集线性划分 也就是说&…

Kubernetes Operator开发实践

Operator 介绍 Operator 可以看成是 CRD Controller 的一种组合资源。Kubernetes 中的基础资源类型有 Pod、Service、Job、Deployment 等表达能力有限&#xff0c;CRD 则提供了创建新的资源类型方式&#xff1b;Controller 监听 CRD 对象实例的增、删、改事件&#xff0c;然后…

GIS在地质灾害危险性评估与灾后重建中的应用

地质灾害是指全球地壳自然地质演化过程中&#xff0c;由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下&#xff0c;地质灾害在世界范围内频繁发生。我国除滑坡灾害外&#xff0c;还包括崩塌、泥石流、地面沉…

GSA、GSEA、ssGSEA、GSVA用到的统计学知识点

文章目录 概率密度函数&#xff08;probability density function&#xff0c;PDF&#xff09;分布函数&#xff08;Cumulative Distribution Function&#xff0c;CDF&#xff09;核密度估计&#xff08;KDE&#xff09;经验累计分布函数&#xff08;Empirical Cumulative Dis…