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

news2024/12/24 10:08:55

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() {

@Override

public 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. 接收数据

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


@Override

public void onReceive(byte[] packet) {

mediaDecodeUtil.decodeFrame(packet);

}

2.创建SurfaceView

在SurfaceView创建后和解码类关联


surface_view.getHolder().addCallback(new SurfaceHolder.Callback() {

@Override

public void surfaceCreated(SurfaceHolder holder) {

Log.d(TAG, "surfaceCreated");

try {

if (mediaDecodeUtil != null)

mediaDecodeUtil.onInit(surface_view);

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

Log.d(TAG, "surfaceChanged");

}

@Override

public 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芯片可用区域的索引,如果有可用,则返回索引,如果没有,则返回 -1

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

}

@Override

public void onOpen(ServerHandshake handshakedata) {

// ...

}

@Override

public void onMessage(String message) {

// ...

}

@Override

public void onMessage(ByteBuffer bytes) {

byte[] buf = new byte[bytes.remaining()];

bytes.get(buf);

if (mCallBack != null)

mCallBack.onClientReceive(buf);

}

@Override

public void onClose(int code, String reason, boolean remote) {

// ...

}

@Override

public void onError(Exception ex) {

// ...

}

}

服务端重点代码


public class MWebSocketServer extends WebSocketServer {

@Override

public void onOpen(WebSocket webSocket, ClientHandshake handshake) {

}

@Override

public void onClose(WebSocket conn, int code, String reason, boolean remote) {

}

@Override

public void onMessage(WebSocket conn, String message) {

}

@Override

public void onError(WebSocket conn, Exception ex) {

}

@Override

public void onStart() {

}

}

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

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

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

相关文章

掌握这几个技术点,你也能开发出爆款ARPG游戏!

在众多ARPG游戏的发售下,游戏市场温度迅速升高,今年很可能会成为一个“ARPG手游大年”,或许会再次出现“神仙打架”的情况。 ARPG作为一种非常经典且流行的游戏类型, 已经诞生过无数经典的作品,比如魂系,暗黑破坏神系列,塞尔达传说系列&#…

网页在特殊日子一键变灰

<template> <div :class"{ grayscale: isGrayscale }"> <!-- 你的页面内容放在这里 --> </div> </template> <script> export default { data() { return { // 存储哀悼日的数组 aidaoriDates:["0404", &q…

Filter过滤器+JWT令牌实现登陆验证

一、背景 我们需要在客户端访问服务器的时候给定用户一定的操作权限&#xff0c;比如没有登陆时就不能进行其他操作。如果他需要进行其他操作&#xff0c;而在这之前他没有登陆过&#xff0c;服务端则需要将该请求拦截下来&#xff0c;这就需要用到过滤器&#xff0c;过滤器可以…

高速缓冲存储器(Cache)

程序访问的局部性原理 程序访问的局部性原理包括时间局部性和空间局部性。 时间局部性&#xff1a;指在最近的未来要用到的信息&#xff0c;很可能是现在正在使用的信息&#xff0c;因为程序中存在循环。空间局部性&#xff1a;指在最近的未来要用到的信息&#xff0c;很可能…

简析内部审计数字化转型的方法和路径【小落送书(第6期)】

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

利用IP地址信息提升网络安全

在计算机网络中&#xff0c;IP地址是用于唯一标识网络设备的重要标识符。然而&#xff0c;由于网络中存在大量设备&#xff0c;有时会出现IP地址冲突的情况&#xff0c;即两个或多个设备在同一网络中使用了相同的IP地址&#xff0c;这可能导致网络连接故障和通信中断。本文将介…

基于PHP的医院绩效管理系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 PHP技术简介 3 1.2 Bootstrap框架简介 3 1.3 MVC技术模式简介 3 1.4 Ajax技术简介 3 1.5 MySQL数据库简介 4 1.6本章小结 4 2 系统需求分析 5 2.1系统分析 5 2.2需求分析 8 2.2.1用户需求分析 8 2.2.2管理员需求分析 10 2…

知识付费开发:开启智慧的新篇章

在数字化时代的浪潮下&#xff0c;知识的获取与分享方式正在发生深刻变革。传统的知识传递模式逐渐被知识付费开发的新模式所取代&#xff0c;它以其独特的魅力和巨大的潜力&#xff0c;正在引领着智慧的新潮流。 知识付费开发&#xff0c;是以用户为中心&#xff0c;以知识为…

Spring Boot工程集成验证码生成与验证功能教程

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

基于SSM技术的宠物寄存系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术介绍 3 1.1 开发技术语言 3 1.1.1 Java 3 1.1.2 Ajax 3 1.1.3 JavaScript 3 1.2 开发框架 4 1.2.1 Spring 4 1.2.2 Spring MVC 4 1.2.3 Mybatis 4 1.2.4 Bootstrap 5 1.3 MySQL数据库 5 1.4 本章小结 6 2 系统分析 7 2.1 系统的需…

针对ETC系统的OBE-SAM模块设计方案

ETC&#xff08;Electrical Toll Collection&#xff09;不停车收费是目前世界上最先进的路桥收费方式。通过安装在车辆挡风玻璃上的车载单元与安装在收费站 ETC 车道上的路侧单元之间的微波专用短程通讯&#xff0c;利用计算机联网技术与银行进行后台结算处理&#xff0c;从而…

ubuntu上通过apt-get 安装指定版本的ecal

在ubuntu上想通过apt get直接安装ecal不自己编译源码安装的话&#xff0c; ecal官网给出的安装指令是 2. Installing eCAL — Eclipse eCAL™ sudo add-apt-repository ppa:ecal/ecal-latest sudo apt-get update sudo apt-get install ecal 要指定版本的时候 按照提示&…

【ICCV】AIGC时代下的SOTA人脸表征提取器TransFace,FaceChain团队出品

一、论文 本文介绍被计算机视觉顶级国际会议ICCV 2023接收的论文 "TransFace: Calibrating Transformer Training for Face Recognition from a Data-Centric Perspective" 论文链接&#xff1a;https://arxiv.org/abs/2308.10133 开源代码&#xff1a;https://an…

在PyCharm中使用Jupyter Notebooks实现高效开发

大家好&#xff0c;在数据科学领域&#xff0c;Jupyter Notebooks已成为一种流行的工具&#xff0c;许多专业人士都在使用它来进行数据分析、机器学习等任务。有时&#xff0c;我们希望在更加强大、功能齐全的IDE环境中运行Jupyter笔记本&#xff0c;以提高工作效率和开发体验。…

GIT | 解决IDEA每次git拉取远程代码 default changelist 都会出现 .idea文件修改记录

问题描述&#xff1a; 每次我在拉取远程代码的时候&#xff0c;git都会默认将 .idea当中的文件&#xff08;例如&#xff1a;compiler.xml or workspace.xml&#xff09;都会莫名其妙的自动修改。 这里吐槽一下很离谱的一个现象&#xff0c;仔细看下修改的内容&#xff0c;最离…

【并查集】一种简单而强大高效的数据结构

目录 一、并查集原理 二、并查集实现 三、并查集应用 1. LeetCode并查集相关OJ题 2. 并查集的其他应用及总结 一、并查集原理 并查集&#xff08;Disjoint Set&#xff09;是一种用来管理元素分组和查找元素所属组别的数据结构。它主要支持两种操作&#xff1a;查找&…

第四篇【传奇开心果系列】Python的自动化办公库技术点案例示例:深度解读Pandas生物信息学领域应用

传奇开心果博文系列 系列博文目录Python的自动化办公库技术点案例示例系列 博文目录前言一、Pandas生物学数据操作应用介绍二、数据加载与清洗示例代码三、数据分析与统计示例代码四、数据可视化示例代码五、基因组数据分析示例代码六、蛋白质数据分析示例代码七、生物医学图像…

LabVIEW管道缺陷智能检测系统

LabVIEW管道缺陷智能检测系统 管道作为一种重要的输送手段&#xff0c;其安全运行状态对生产生活至关重要。然而&#xff0c;随着时间的推移和环境的影响&#xff0c;管道可能会出现老化、锈蚀、裂缝等多种缺陷&#xff0c;这些缺陷若不及时发现和处理&#xff0c;将严重威胁到…

阿珊比较Vue和React:两大前端框架的较量

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

linux下访问MySQL,检索数据库库表字段报错 Public Key Retrieval is not allowed(不允许公钥检索)

报错如下&#xff1a; 解决办法 在连接数据库的配置文件中加上&allowPublicKeyRetrievaltrue语句&#xff0c;如下&#xff1a; jdbc:mysql://localhost:3306?useUnicodetrue&zeroDateTimeBehaviorconvertToNull&autoReconnecttrue&characterEncodingutf-8&…