万象园网站建设与开发,温州建设企业网站,域名备案掉了网站还可以用,公司网站打开显示建设中Android屏幕共享-硬编码硬解码 说起Android之间的屏幕共享#xff0c;第一次接触会比较陌生#xff0c;不过大家多少有了解过ffmpeg#xff0c;看上去是不是很熟悉#xff1f;ffmpeg是一套处理音视频的开源程序#xff0c;但对于C了解较少的同学#xff0c;编译起来很复杂…Android屏幕共享-硬编码硬解码 说起Android之间的屏幕共享第一次接触会比较陌生不过大家多少有了解过ffmpeg看上去是不是很熟悉ffmpeg是一套处理音视频的开源程序但对于C了解较少的同学编译起来很复杂。 有同学问有没有纯JAVA操作的方法呢还真有。
一、效果图 Demo界面 二、软解码和硬解码
软解码
利用CPU的计算进行解码比如使用FFmpeg解码由于解码是通过CPU运算所以加大CPU负担增加耗电。
硬解码
利用手机自带处理视频的芯片专门模块编码进行解码如 dsp。对CPU要求比较低主要依赖于硬件所以解码芯片在不同的手机上表现可能会有不一致的情况。好处是硬解由于是单独的处理芯片所以速度比软解码要快。
三、代码分析
3.1 Android 硬编码
硬编码主要是使用MediaCodec访问底层的codec来实现编解码它是Android提供的用于对音视频进行编解码的类。
整体来说步骤分为以下步骤 详细点的代码如下
申请录屏权限 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);
配置并获取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 硬解码
整体来说步骤分为以下步骤 详细代码步骤如下
接收数据
将接收到的二进制数据流放进解码的工具类 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。
首先需要引入依赖因为不属于原生的范畴
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↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓