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

@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)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙系统:揭秘前端开发的新机遇

众所周知,华为开发者大会2023,宣布不再兼容安卓,同时宣布了“鸿飞计划”,欲与iOS、安卓在市场三分天下,这对中国国产操作系统而言,具有划时代的意义。 鸿蒙应用开发的兴起&发展 鸿蒙操作系统是华为自…

旅游陪同翻译,英译中需要具备什么能力!

随着中国旅游业的蓬勃发展,越来越多的外国游客选择踏足这片充满魅力的土地。然而,语言障碍常常成为他们探索中国文化的绊脚石。在这时,旅游陪同翻译应运而生,为游客提供了便捷的语言沟通服务,让他们的旅程更加顺畅和愉…

NFT Insider #122:OpenSea与科切拉音乐节合作推出NFT系列,Flowty联合创始人购入勒布朗・詹姆斯NFT

引言:NFT Insider由NFT收藏组织WHALE Members (https://twitter.com/WHALEMembers)、BeepCrypto (https://twitter.com/beep_crypto) 联合出品,浓缩每周NFT新闻,为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周报将从NFT市场数据&a…

借着ChatGPT的人机交互聊聊长连接

ChatGPT这两年可谓风靡全球,尤其是最近Sora视频模型的横空出世以及claude 3模型所具备的浅意识,更是像打开了新世界的大门。本文就从ChatGPT的网页聊天开始聊起(有蹭热度之嫌,哈哈),聊聊长连接的发展历程和…

13 OpenCv自定义线性滤波

文章目录 卷积算子示例 卷积 卷积是图像处理中一个操作,是kernel在图像的每个像素上的操作。Kernel本质上一个固定大小的矩阵数组,其中心点称为锚点(anchor point) 把kernel放到像素数组之上,求锚点周围覆盖的像素乘积之和(包括锚…

几种常见的python开发工具

​ Python是一种功能强大且易于学习的编程语言,被广泛应用于数据科学、机器学习、Web开发等领域。随着Python在各个领域的应用越来越广泛,越来越多的Python开发工具也涌现出来。但是,对于新手来说,选择一款合适的Python开发工具可…

vue3引入高德地图

首先注册高德key https://console.amap.com/dev/key/a vue项目中安转地图包 pnpm i amap/amap-jsapi-loader -S 先说最重要核心,踩雷过 页面中需写入以下代码,现在注册的高德key要求强制写入安全密钥 window._AMapSecurityConfig {securityJsCode…

9、字符串插入和删除

#include <iostream>using namespace std;void test01 () {string s "hello";s.insert (1, "111");cout << s << endl;s.erase(1, 3);cout << s << endl; }int main () {test01();return 0; } 总结&#xff1a; 插入和删除…

基于SVM模型的网络入侵检测模型训练与评估(NSL-KDD数据集)

简介 针对网络安全领域的NSL-KDD数据集进行分类任务的预处理和模型训练、以及超参数调优。 数据预处理 读取并解析数据集&#xff1b;检查并删除指定列&#xff08;outcome&#xff09;的缺失值&#xff1b;对类别型特征&#xff08;protocol_type, service, flag&#xff0…

Linux第73步_学习Linux设备树和“OF函数”

掌握设备树是 Linux驱动开发人员必备的技能&#xff01; 1、了解设备树文件 在3.x版本以前的Linux内核源码中&#xff0c;存在大量的“arc/arm/mach-xxx”和“arc/arm/plat-xxx”文件夹&#xff0c;里面很多个“.c”和“.h”文件&#xff0c;它们用来描述设备信息。而现在的A…

深度学习与人类的智能交互:迈向自然与高效的人机新纪元

引言 随着科技的飞速发展&#xff0c;深度学习作为人工智能领域的一颗璀璨明珠&#xff0c;正日益展现出其在模拟人类认知和感知过程中的强大能力。本文旨在探讨深度学习如何日益逼近人类智能的边界&#xff0c;并通过模拟人类的感知系统&#xff0c;使机器能更深入地理解和解…

深空通信DTN总结

这里写自定义目录标题 A novel Federated Computation approach for Artificial Intelligence applications in Delay and Disruption Tolerant NetworksabstractintroductionDELAY AND DISRUPTION TOLERANT NETWORKS联邦计算用于容忍延迟和干扰的网络的联合学习框架DTN-ML Orc…

【视频图像取证篇】Impress模糊图像增强技术之颜色滤波器场景实例教程(蘇小沐)

【视频图像取证篇】Impress模糊图像增强技术之颜色滤波器场景实例教程&#xff08;蘇小沐&#xff09; Impress模糊图像增强技术之颜色滤波器场景实例教程—【蘇小沐】 1、实验环境 系统环境Impress&#xff0c;[v8.2.02]Windows 11 专业版&#xff0c;[23H2&#xff08;226…

犀牛7-软件基础设置

一、刚打开页面时&#xff0c;会弹出模板文件&#xff0c;一般我们选择小模型-毫米&#xff0c; 我们点击小模型-毫米之后&#xff0c;界面是这样的。 菜单栏&#xff1a;我们比较少使用&#xff0c;一般就用到创建文件。 命令栏(非常重要)&#xff1a;1、记录我们使用过的工…

httprunner参数化

1. 示例 引入对应的Parameters 1.1. CSV参数 from httprunner import HttpRunner, Config, Step, RunRequest, Parameters pytest.mark.parametrize("param", Parameters({"mobile_phone-pwd": "${P(csv_data/mobile_phone-pwd.csv)}"}))def …

项目解决方案:多地5G蓄能电站的视频监控联网系统设计方案

目 录 一、前言 二、系统架构设计 1、系统架构设计说明 2、系统拓扑图 三、关键技术 1. 5G支持技术 2. 视频图像处理技术 3. 数据融合与分析技术 四、功能特点 1. 高效可靠 2. 实时监测 3. 远程控制 4. 故障预测 五、应用前景 一、前言 随着能源…

讲讲 SaaS 平台的多租户设计

本篇就来讲讲 SaaS 平台的多租户设计。 以“钉钉”为例看实际的多租户场景 在讲设计之前&#xff0c;我们先以“钉钉”为例&#xff0c;来看看一个 SaaS 平台是如何运作的。相信大部分B 端产品经理都体验过钉钉&#xff0c;我们分两个维度来讲钉钉的租户注册到使用的流程。一…

w022郑州大学招新赛选拔赛

A-SW的与众不同数组_2022学年第一学期郑州大学ACM招新赛&选拔赛 (nowcoder.com) #include <bits/stdc.h> #define int long long using namespace std;void solve(){int n;cin >> n;vector<int> v;for(int i 1; i < n; i){int x;cin >> x;v.p…

导出谷歌gemma模型为ONNX

参考代码如下&#xff08;从GitHub - luchangli03/export_llama_to_onnx: export llama to onnx修改而来&#xff0c;后面会合入进去&#xff09; 模型权重链接参考&#xff1a; https://huggingface.co/google/gemma-2b-it 可以对modeling_gemma.py进行一些修改(transforme…

docker搭建dashdot

Dashdot 是一个指标收集工具&#xff0c;用于报告 Kubernetes 集群中的资源使用情况。假设你想要使用 Docker 来搭建 Dashdot&#xff0c;你需要制作或获取一个 Dashdot 的 Docker 镜像&#xff0c;然后可以通过 Docker CLI 命令或者使用 Docker Compose 来配置和运行这个容器。…