1、在布局xml文件中设置根布局对象id。
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:id="@+id/layoutRoot"
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background="@drawable/bg_home"
tools:context=".ui.MainActivity">
2、在Activity加载对象
private var isStopRemote = false // 控制是否退出远程截屏
private var isRemoteRunning = false //
private lateinit var layoutRoot: ConstraintLayout
layoutRoot = this.findViewById(R.id.layoutRoot) as ConstraintLayout
3、在Acitivity中调用接口获取改对象的显示图像
private fun startRemoteScreen() {
if(isRemoteRunning) {
return
}
isStopRemote = false
isRemoteRunning = true
//layoutRoot.background.setDither(true)
GlobalScope.launch(Dispatchers.Main) {
while(!isStopRemote)
try {
if(layoutRoot != null) {
layoutRoot.setDrawingCacheEnabled(true)
layoutRoot.buildDrawingCache()
delay(100)
layoutRoot.getDrawingCache()
}
} catch (ex: Exception) {
} finally {
try {
if(layoutRoot != null) {
layoutRoot.destroyDrawingCache()
layoutRoot.setDrawingCacheEnabled(false)
}
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
}
delay(100)
}
}
isRemoteRunning = false
}
4、在model方法中处理合成图像并传输
private val screenWidth = GwApplication.SCREEN_WIDTH
private val screenHeight = GwApplication.SCREEN_HEIGHT
private var remoteImg: Bitmap? = Bitmap.createBitmap(screenWidth / 2, screenHeight / 2, Bitmap.Config.ARGB_8888)
private var remoteCanvas = Canvas(remoteImg!!)
private var output = ByteArrayOutputStream()
private var imgPath: String? = null
public fun remoteScreenRecord(bmp: Bitmap?) {
if (bmp == null || bmp.isRecycled || remoteImg == null) {
return
}
remoteCanvas.drawColor(Color.WHITE)
val count = remoteCanvas.save()
try {
remoteCanvas.drawBitmap(bmp, Rect(0, 0, screenWidth, screenHeight), Rect(0, 0, screenWidth / 2, screenHeight / 2), Paint())
remoteCanvas.save()
output.reset()
remoteImg!!.compress(Bitmap.CompressFormat.JPEG, 80, output)
output.flush()
sendRemoteScreenHidMsg(remoteImg!!.width, remoteImg!!.height, output.toByteArray())
} catch (ex: Exception) {
ex.printStackTrace()
} finally {
remoteCanvas.restoreToCount(count)
}
}
5、Android端显示效果:
6、PC端显示效果(压缩图像50%)
7、摄像头视频图像合成
public fun remoteScreenRecord(bmp: Bitmap?, imgData: ImageDoubleData) {
if(bmp == null || bmp.isRecycled || remoteImg == null) {
return
}
remoteCanvas.drawColor(Color.WHITE)
val count = remoteCanvas.save()
var tmp: Bitmap? = null
try {
remoteCanvas.drawBitmap(bmp, Rect(0, 0, screenWidth, screenHeight), Rect(0, 0, screenWidth / 2, screenHeight / 2), Paint())
tmp = fastYUVtoRGB.convertYUVtoRGB(imgData.getTemp(), imgData.width, imgData.height)
log("====fastYUVtoRGB.convertYUVtoRGB() temp is null?${tmp != null}")
if (tmp != null) {
log("====remoteCanvas.drawBitmap(tmp)")
// 画入视频图像,注意划入的视频大小和定位
remoteCanvas.drawBitmap(tmp, Rect(0, 0, imgData.width, imgData.height), Rect(15, 62, 372 + 22, 279 + 63), Paint())
}
remoteCanvas.save()
output.reset()
remoteImg!!.compress(Bitmap.CompressFormat.JPEG, 80, output)
output.flush()
sendRemoteScreenHidMsg(remoteImg!!.width, remoteImg!!.height, output.toByteArray())
} catch (ex: Exception) {
ex.printStackTrace()
} finally {
remoteCanvas.restoreToCount(count)
if(tmp != null && !tmp.isRecycled) {
tmp.recycle()
tmp = null
}
}
}
8、合成后的显示效果
相关代码:
Activity界面
private lateinit var cameraView: CameraDoubleView
cameraView = findViewById(R.id.cameraView) as CameraDoubleView
private fun initCameraView(width: Int, height: Int) {
cameraView.initCameraView(GwApplication.DEFAULT_CR_CAMERA, 0, width, height)
}
// 显示摄像头
initCameraView(mWidth, mHeight);
ImageDoubleData .java文件
/**
-
author zoufeng
-
date:2021/11/18
-
desc:
*/
public class ImageDoubleData {
private int width;
private int height;
private int size;
private byte[] data;
private byte[] temp;
private Boolean isNewData;
private Boolean isNewTemp;public ImageDoubleData(int width, int height) {
this.width = width;
this.height = height;
size = width * height * 3 / 2;
data = new byte[size];
temp = new byte[size];
isNewData = true;
isNewTemp = true;
}public int getWidth() {
return width;
}public int getHeight() {
return height;
}public byte[] getData() {
return data;
}public void setData(byte[] pData) {
synchronized (isNewData) {
if (!isNewData) {
System.arraycopy(pData, 0, data, 0, pData.length);
setNewData(true);
}
}
}public void setTemp(byte[] pData) {
synchronized (isNewTemp) {
if (!isNewTemp) {
System.arraycopy(pData, 0, temp, 0, pData.length);
setNewTemp(true);
}
}
}public byte[] getTemp() {
return temp;
}public boolean isNewData() {
return isNewData;
}public boolean isNewTemp() {
return isNewTemp;
}public synchronized void setNewData(boolean isNew) {
isNewData = isNew;
}public synchronized void setNewTemp(boolean isNew) {
isNewTemp = isNew;
}
}
CameraDoubleView.java文件
import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.face.obj.ImageData;
import com.face.obj.ImageDoubleData;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
-
author zoufeng
-
date:2021/11/18
-
desc:
*/
public class CameraDoubleView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {
private final static String TAG = “CameraView”;
private SurfaceHolder surfaceHolder;
private int mCameraIndex = 0;
private Camera mCamera;
private int mRotate = 0;
private int mWidth = 0;
private int mHeight = 0;
private int mSize = 0;
private ArrayList<Camera.Size> listSupportSize = new ArrayList<>();private CameraView.onPreviewChangedListener mOnPreviewChangedListener = null;
private ImageDoubleData imgData = null;
private byte[] mBuffer = null;
private boolean isPreview = false;public CameraDoubleView(Context context, AttributeSet attrs) {
super(context, attrs);
}/**
- @param cameraIndex 摄像头序号
- @param rotate 旋转角度
- @param width 图像preview宽度
- @param height 图像preview高度
*/
public void initCameraView(int cameraIndex, int rotate, int width, int height){
log(String.format(“CameraView initCameraView() cameraIndex:%d rotate:%d width:%d height:%d”, cameraIndex, rotate, width, height));
surfaceHolder = this.getHolder();
surfaceHolder.addCallback(this);
mCameraIndex = cameraIndex;
mRotate = rotate;
mWidth = width;
mHeight = height;
mSize = mWidth * mHeight * 3 / 2;
mBuffer = new byte[mSize];
imgData = new ImageDoubleData(mWidth, mHeight);
isPreview = false;
}
/**
-
初始化SurfaceView时调用一次,另外更改surface或者onpause->onresume时调用
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
log(“CameraView surfaceChanged()”);
if (null == mCamera) {
//Camera.open()方法说明:2.3以后支持多摄像头,所以开启前可以通过getNumberOfCameras先获取摄像头数目,
// 再通过 getCameraInfo得到需要开启的摄像头id,然后传入Open函数开启摄像头,
// 假如摄像头开启成功则返回一个Camera对象
try {
mCamera = Camera.open(mCameraIndex);
} catch (Exception e) {
}
}
if (mCamera != null) {
int num = Camera.getNumberOfCameras();
log(“Camera.getNumberOfCameras() num:” + num);
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> sizeList = parameters.getSupportedPictureSizes();
log(“getSupportedPictureSizes() sizeList.size:” + sizeList.size());
listSupportSize.clear();
listSupportSize.addAll(sizeList);//设置照片的大小 log("setPictureSize mWidth:" + mWidth + " mHeight:" + mHeight); parameters.setPreviewSize(mWidth , mHeight); parameters.setPictureSize(mWidth , mHeight); //预览画面默认是横屏的,需要旋转90度 if(mRotate != 0) { mCamera.setDisplayOrientation(mRotate); } try { mCamera.setParameters(parameters); } catch(RuntimeException ex) { ex.printStackTrace(); } try { mCamera.setPreviewDisplay(holder); mCamera.addCallbackBuffer(mBuffer); mCamera.setPreviewCallbackWithBuffer(this); } catch (IOException e) { e.printStackTrace(); } //调用相机预览功能 mCamera.startPreview();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
log(“CameraView surfaceCreated()”);
}@Override
public void surfaceDestroyed(SurfaceHolder holder) {
log(“CameraView surfaceDestroyed()”);
isPreview = false;
if (null != mCamera) {
try {
mCamera.setPreviewDisplay(null);
mCamera.setPreviewCallbackWithBuffer(null);
} catch (IOException e) {
e.printStackTrace();
}
//停止预览
mCamera.stopPreview();
//释放相机资源
mCamera.release();
mCamera = null;
}
}@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//log("====onPreviewFrame()");
imgData.setData(data);
imgData.setTemp(data);
mCamera.addCallbackBuffer(mBuffer);}
public void setOnPreviewChangedListener(CameraView.onPreviewChangedListener mPreviewChangedListener) {
log(“CameraView setOnPreviewChangedListener()”);
this.mOnPreviewChangedListener = mPreviewChangedListener;
}public interface onPreviewChangedListener {
public void onPreviewChanged(int cameraIndex, boolean isPreview);
}public ImageDoubleData getImageData() {
return imgData;
}public int getPreviewWidth() {
return mWidth;
}public int getPreviewHeight() {
return mHeight;
}public boolean isPreview() {
return isPreview;
}public List<Camera.Size> getListSupportSize() {
return listSupportSize;
}private void log(String msg) {
Log.d(TAG, msg);
}
}