本章基于前章基础上功能 继续优化,
主要优化点 1 优化取中间值 预览画面的尺寸与帧数
2 YUV_420_888toNV21 用c++ 重写,提升效率
1 上全部代码
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
package com.unity3d.player;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents {
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
// The command line arguments are passed as a string, separated by spaces
// UnityPlayerActivity calls this from 'onCreate'
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
// @param cmdLine the current command line arguments, may be null
// @return the modified command line string or null
protected String updateUnityCommandLineArguments(String cmdLine) {
return cmdLine;
}
// Setup activity layout
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
getIntent().putExtra("unity", cmdLine);
mUnityPlayer = new UnityPlayer(this, this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
applyForPermission(); // add by eh
}
// When Unity player unloaded move task to background
@Override
public void onUnityPlayerUnloaded() {
moveTaskToBack(true);
}
// Callback before Unity player process is killed
@Override
public void onUnityPlayerQuitted() {
}
@Override
protected void onNewIntent(Intent intent) {
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent);
mUnityPlayer.newIntent(intent);
}
// Quit Unity
@Override
protected void onDestroy() {
mUnityPlayer.destroy();
super.onDestroy();
}
// Pause Unity
@Override
protected void onPause() {
super.onPause();
mUnityPlayer.pause();
}
// Resume Unity
@Override
protected void onResume() {
super.onResume();
mUnityPlayer.resume();
}
// Low Memory Unity
@Override
public void onLowMemory() {
super.onLowMemory();
mUnityPlayer.lowMemory();
}
// Trim Memory Unity
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
mUnityPlayer.lowMemory();
}
}
// This ensures the layout will be correct.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
// Notify Unity of the focus change.
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mUnityPlayer.injectEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return mUnityPlayer.injectEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mUnityPlayer.injectEvent(event);
}
/*API12*/
public boolean onGenericMotionEvent(MotionEvent event) {
return mUnityPlayer.injectEvent(event);
}
//增加的代码开始处
final private static String TAG = UnityPlayerActivity.class.getName();
//供unity 调用 接口
public void OnClickBtn1() {
Log.d(TAG, "android OnClickBtn1");
if (isOpenFloatingWindow()) {
addFloatingWindowView(); //增加悬浮窗后
} else {
openCamera();
}
}
//供unity 调用 接口
public void OnClickBtn2() {
Log.d(TAG, "android OnClickBtn2");
if (isOpenFloatingWindow()) {
releaseFloatingWindow();
} else {
releaseCamera();
}
}
//供unity 调用 接口 暂时无用
public void OnClickBtn3() {
Log.d(TAG, "android OnClickBtn3");
// myActivity.addFloatingWindowView();
exchangeCamera(); //切换相机
}
/
//增加camera2 摄像头
private CameraManager mCameraManager;
private CameraDevice.StateCallback mStateCallback;
private CameraDevice mCameraDevice;
private TextureView mTextureView;
private CameraCaptureSession mCaptureSession;
private CaptureRequest mPreviewRequest;
private int mCameraSensorOrientation = 0; //摄像头方向
// private int mCameraFacing = CameraCharacteristics.LENS_FACING_FRONT ; //默认使用前置摄像头
private int mDisplayRotation = 0; //getDisplay().defaultDisplay.rotation //手机方向
private String cameraIdFront = "1"; //相机ID 可以自行设定 // 0 后置 1 前置 //默认使用前置摄像头
private Size[] cameraSizeFront; //相机支持的尺寸
private CameraCharacteristics cameraCharacteristicsFront; //
private static final int RECV_MAX_IMAGES = 2; //同时最多接收2张图片
private ImageReader mImageReader;
private TextureView.SurfaceTextureListener surfaceTextureListener;
private ImageReader.OnImageAvailableListener imageAvailableListener;
private boolean mFlashSupported = false; //判断是否支持闪关灯
android.util.Range<Integer> mAverageFPS = null; //中间帧率
private HandlerThread mBackgroundThread; //用于运行不应阻塞UI的任务的附加线程
private Handler mBackgroundHandler; //用于在后台运行任务的{@link Handler}
//悬浮窗相关
private WindowManager windowManager = null;
private WindowManager.LayoutParams layoutParams = null;
// public static final int DMS_INPUT_IMG_W = 640;
// public static final int DMS_INPUT_IMG_H = 480;
public static final int PREVIEW_WIDTH = 640;// //预览的宽度test 680;
public static final int PREVIEW_HEIGHT = 480; //预览的高度
public static final int LAYOUT_WIDTH = 640; //预览的宽度
public static final int LAYOUT_HEIGHT = 480; //预览的高度
public static final int DEFAULT_SCREEN_X = 300; //默认开始位置
public static final int DEFAULT_SCREEN_Y = 300; //默认开始位置
public static final int DEFAULT_CAMERA_FPS = 30; //默认摄像头帧数 // 20-40
private Size mPreviewSize = null; //预览的尺寸 默认 PREVIEW_WIDTH PREVIEW_HEIGHT
private Size mLayoutSize = null; //布局尺寸 view尺寸跟布局 match_parent 所以一样大
private Size mScreenSize = null; //屏幕尺寸
private View mPreviewdisplayView = null; //预览窗口
private Surface mPreviewSurface; //预览窗口surface
// LayoutInflater floatingWindowlayoutInflater = null ; //悬浮窗口布局填充器 //放这里也可以,用临时变量也可以的
private final boolean bOpenFloatingWindowFlag = true; //是否开启悬浮窗口 //不开启只做数据分析
private String[] mAllCameraIds = null; //所有摄像头
//
//权限 相关
private final int REQUEST_CODE_CONTACT = 1;
final String[] AppPermissions = {
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
// Manifest.permission.READ_EXTERNAL_STORAGE,
// Manifest.permission.READ_PHONE_STATE,
Manifest.permission.INTERNET,
Manifest.permission.CAMERA,
//Manifest.permission.RECORD_AUDIO,
};
///
//是否开启悬浮窗口
private boolean isOpenFloatingWindow() {
return bOpenFloatingWindowFlag;
}
// private boolean getWindowManger(){
//
// windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); //在哪都能有, 下面只有在Activity及继续类里有
// // windowManager = getWindowManager() ; //上面1句 跟这句 是一样的 Activity里 自带
// return windowManager
// }
//创建悬浮窗布局
void createFloatingWindowLayout(){
layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; //TYPE_APPLICATION_SUB_PANEL ; //TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = getLayoutWidth(); // //窗口宽度
layoutParams.height = getLayoutHeight();// //窗口高度
layoutParams.x = DEFAULT_SCREEN_X; //开始的位置
layoutParams.y = DEFAULT_SCREEN_Y; //开始的位置
}
//更新预览框大小 当默认的窗口尺寸不存在时 重新设置个尺寸
private void updateFloatingWindowLayout(int w,int h){
if(layoutParams != null){
// layoutParams.width = getPreviewWidth(); // PREVIEW_WIDTH; //窗口宽度
//layoutParams.height = getPreviewHeight();//PREVIEW_HEIGHT; //窗口高度
layoutParams.width = w ; //窗口宽度
layoutParams.height = h ;//窗口高度
}
}
//增加悬浮窗口
void addFloatingWindowView(){
LayoutInflater floatingWindowlayoutInflater = LayoutInflater.from(this);
mPreviewdisplayView = floatingWindowlayoutInflater.inflate(R.layout.camera_display, null);
mPreviewdisplayView.setOnTouchListener(new FloatingOnTouchListener());
mTextureView = mPreviewdisplayView.findViewById(R.id.preview);
/
//因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
// final int w = getPreviewWidth() ;
// final int h = getPreviewHeight() ;
// Matrix matrix = configureTransform(w,h,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
// if(matrix != null) mTextureView.setTransform(matrix);
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
final int wl = getLayoutWidth() ;
final int hl = getLayoutHeight();
Matrix matrix = configureTransform(wl,hl,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
if(matrix != null) mTextureView.setTransform(matrix);
/
mTextureView.setSurfaceTextureListener(surfaceTextureListener);
windowManager.addView(mPreviewdisplayView, layoutParams);
}
//释放悬浮窗口 并停止 相机
void releaseFloatingWindow(){
if(windowManager != null && mPreviewdisplayView !=null){
windowManager.removeView(mPreviewdisplayView);
mPreviewdisplayView = null ;
}
releaseCamera();
}
private void doSomeThing(){
if(initCameraInfo(getDefaultCameraID())){
initSurfaceTextureListener(); //监听SurfaceTexture
initImageAvailableListener(); //图像监听,也可以放到createImageReader前
createFloatingWindowLayout(); //创建布局
}
}
//申请权限
private void applyForPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (String str : AppPermissions) {
if (ContextCompat.checkSelfPermission(this, str) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, AppPermissions, REQUEST_CODE_CONTACT);
return;
}
}
//do some thing
doSomeThing();
}else{
//使用的比Build.VERSION_CODES.M大 所以这里不可能走到
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_CONTACT) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//do some thing
doSomeThing();
} else if (grantResults.length == 0) {
// 已知魅族手机会进这个结果
} else {
Toast.makeText(this, "您拒绝了相关权限,无法使用!", Toast.LENGTH_SHORT).show();
finish();
}
}
}
///相机相关//
//启动相机前 开启
private void startBackgroundThread() {
if (mBackgroundThread == null || mBackgroundHandler == null) {
Log.v(TAG, "startBackgroundThread");
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
//手动停止 或 onDestroy 停止
private void stopBackgroundThread() {
Log.v(TAG, "stopBackgroundThread");
if(mBackgroundThread != null){
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//释放 与切换
private void releaseCamera() {
if(mCaptureSession!=null){
mCaptureSession.close();
mCaptureSession = null ;
}
if(mCameraDevice !=null){
mCameraDevice.close();
mCameraDevice = null;
}
if(mImageReader !=null){
mImageReader.close();
mImageReader = null ;
}
stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()
}
private Handler getBackgroundHandler(){
return mBackgroundHandler ;
}
//切换相机 //直接点击事件下调用即可
private void exchangeCamera() {
if(windowManager == null) return;
//前后置相机切换
// mCameraFacing = mCameraFacing == CameraCharacteristics.LENS_FACING_FRONT?CameraCharacteristics.LENS_FACING_BACK:CameraCharacteristics.LENS_FACING_FRONT ;
swapDefaultCameraID(); //切换相机
releaseCamera();
if(initCameraInfo(getDefaultCameraID()) ){
if(mTextureView != null){
/
//因为 TextureView 没问有具体尺寸,只是 匹配(match_parent) mPreviewdisplayView ,而 mPreviewdisplayView 尺寸 跟mPreviewSize 一样的所以 第1,2参数全 mPreviewSize的 宽高
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
final int wl = getLayoutWidth() ;
final int hl = getLayoutHeight();
configureTransform(wl,hl,w ,h,windowManager.getDefaultDisplay().getRotation()) ; //旋转角度根据屏幕
/
}
openCamera();
}
}
///
//初始化Surface 监听
void initSurfaceTextureListener(){
surfaceTextureListener= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
// SurfaceTexture texture = mTextureView.getSurfaceTexture(); //也可以的
SurfaceTexture texture = surfaceTexture ; //感觉只有一个TextureView surfaceTexture == mTextureView.getSurfaceTexture() 用那个都可以
final int w = getPreviewWidth() ;
final int h = getPreviewHeight() ;
texture.setDefaultBufferSize(w, h); //这里会重新设置尺寸
mPreviewSurface = new Surface(texture);
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
};
}
// private boolean initWindowManager(Context context){
// if(windowManager == null){
// if (context instanceof Activity) {
// Activity activity = (Activity) context;
// windowManager = activity.getWindowManager();
// } else {
// windowManager = (WindowManager) context.getSystemService(UnityPlayerActivity.WINDOW_SERVICE);
// }
// }
// return windowManager !=null ;
// }
// //得到屏幕旋转角度
// private int getDisplayRotation(Context context) {
// if(initWindowManager(context))
// {
// int rotation = windowManager.getDefaultDisplay().getRotation();
// switch (rotation) {
// case Surface.ROTATION_0:
// return 0;
// case Surface.ROTATION_90:
// return 90;
// case Surface.ROTATION_180:
// return 180;
// case Surface.ROTATION_270:
// return 270;
// }
// }
// return 0;
// }
//
// //得到屏幕信息
// private Display getDisplay(Context context) {
// if(initWindowManager(context)) return windowManager.getDefaultDisplay() ;
// return null;
// }
//得到屏幕信息
private boolean getScreenDisplayInfo(){
windowManager = (WindowManager)getSystemService(UnityPlayerActivity.WINDOW_SERVICE);
if(windowManager == null) return false ;
DisplayMetrics outMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(outMetrics);
if(outMetrics.widthPixels < 1 || outMetrics.heightPixels < 1){
return false ;
}
mScreenSize = new Size(outMetrics.widthPixels,outMetrics.heightPixels);
outMetrics = null ;
return true;
}
//预览图像旋转度根据屏幕旋(横竖)转度 //displayRotation 1 3 横屏 0 2 竖屏
private Matrix configureTransform(final int viewWidth, final int viewHeight,final int previewWidth,final int previewHeight,final int displayRotation) {
int rotation = displayRotation ;
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, previewHeight, previewWidth);
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
Matrix matrix = new Matrix();
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / previewHeight,
(float) viewWidth / previewWidth);
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
return matrix ;
}
return null ;
}
// private void configureTransform(int viewWidth, int viewHeight) {
// if (null == mTextureView || null == mPreviewSize || null == windowManager) {
// return;
// }
// // int rotation = getWindowManager().getDefaultDisplay().getRotation();
// int rotation = windowManager.getDefaultDisplay().getRotation();
// Matrix matrix = new Matrix();
// RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
// RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
// float centerX = viewRect.centerX();
// float centerY = viewRect.centerY();
// if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
// bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
// matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
// float scale = Math.max(
// (float) viewHeight / mPreviewSize.getHeight(),
// (float) viewWidth / mPreviewSize.getWidth());
// matrix.postScale(scale, scale, centerX, centerY);
// matrix.postRotate(90 * (rotation - 2), centerX, centerY);
// }
// mTextureView.setTransform(matrix);
// }
private class CompareSizeByArea implements java.util.Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight()
- (long) rhs.getWidth() * rhs.getHeight());
}
}
private int getPreviewWidth(){
return mPreviewSize!=null?mPreviewSize.getWidth():PREVIEW_WIDTH ;
}
private int getPreviewHeight(){
return mPreviewSize!=null?mPreviewSize.getHeight():PREVIEW_HEIGHT;
}
private int getLayoutWidth(){
return mLayoutSize!=null?mLayoutSize.getWidth():LAYOUT_WIDTH ;
}
private int getLayoutHeight(){
return mLayoutSize!=null?mLayoutSize.getHeight():LAYOUT_HEIGHT;
}
private int getScreenWidth(){
return mScreenSize!=null?mScreenSize.getWidth():PREVIEW_WIDTH ;
}
private int getScreenHeight(){
return mScreenSize!=null?mScreenSize.getHeight():PREVIEW_HEIGHT;
}
//是否支持闪光灯模式
private boolean isFlashSupported(){
return mFlashSupported ;
}
//得到所有摄像头ID
private String [] getAllCameraIds(){
if(mCameraManager == null) mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
try {
String [] carmeraids = mCameraManager.getCameraIdList() ;
return carmeraids ;
}catch (CameraAccessException e){
Log.e(TAG,e.getMessage());
}
return null ;
}
//检测相机ID是否存在
private boolean checkCameraExistByCameraID(String strCameraID){
if(mAllCameraIds == null){
mAllCameraIds = getAllCameraIds();
}
if(mAllCameraIds != null && mAllCameraIds.length > 0){
for (String cameraid : mAllCameraIds){
if(cameraid.equals(strCameraID)){
return true;
}
}
}
return false ;
}
//通过ID 得到 该摄像头特征属性
private CameraCharacteristics getCameraCharacteristicsByCameraID(String strCameraId){
if(mCameraManager == null) return null ;
try{
return mCameraManager.getCameraCharacteristics(strCameraId);
} catch (CameraAccessException e){
Log.e(TAG,e.getMessage());
}
return null ;
}
private Size getSuitableLayoutSize(Size previewSize,int ratio,int screenW,int screenH ){
final Size s4_3 = new Size(320,240); //80
final Size s3_2 = new Size(320,160);
final Size s16_10 = new Size(320,200);
final Size s16_9 = new Size(320,180);
final int ratio4_3 = 13 ; //1.333*10
final int ratio3_2 = 15 ;//1.5*10
final int ratio16_10 = 16 ; //1.6*10
final int ratio16_9 = 17 ;//1.777*10
final int screenratio = screenW/320 ;
if(screenratio >= 4){
switch (ratio){
case ratio4_3:{
return new Size(s4_3.getWidth()*2,s4_3.getHeight()*2) ;
}
case ratio3_2:{
return new Size(s3_2.getWidth()*2,s3_2.getHeight()*2) ;
}
case ratio16_10:{
return new Size(s16_10.getWidth()*2,s16_10.getHeight()*2) ;
}
case ratio16_9:{
return new Size(s16_9.getWidth()*2,s16_9.getHeight()*2) ;
}
}
}else {
switch (ratio){
case ratio4_3:{
return new Size(s4_3.getWidth(),s4_3.getHeight()) ;
}
case ratio3_2:{
return new Size(s3_2.getWidth(),s3_2.getHeight()) ;
}
case ratio16_10:{
return new Size(s16_10.getWidth(),s16_10.getHeight()) ;
}
case ratio16_9:{
return new Size(s16_9.getWidth(),s16_9.getHeight()) ;
}
}
}
//这个会拉伸
return new Size(LAYOUT_WIDTH,LAYOUT_HEIGHT) ;
}
//得到合适的预览尺寸 //暂时只针对横屏 //0 为最大尺寸
private boolean getSuitablePreviewSize(Size [] outputSizes){
if(null == outputSizes) return false ;
if(1 > outputSizes.length) return false ;
//4:3 3:2 16:10 16:9 //4种
final Size s4_3 = new Size(320,240); //80
final Size s3_2 = new Size(320,160);
final Size s16_10 = new Size(320,200);
final Size s16_9 = new Size(320,180);
final int ratio4_3 = 13 ; //1.333*10
final int ratio3_2 = 15 ;//1.5*10
final int ratio16_10 = 16 ; //1.6*10
final int ratio16_9 = 17 ;//1.777*10
// final Size[] screenSizes = new Size []{s4_3,s3_2,s16_10,s16_9};
final int screenW = getScreenWidth() ;
final int screenH = getScreenHeight() ;
final int screenTotalPixel = screenW*screenH ;
final float rangemin = 0.1f ; // 1/10 to 1/6
final float rangeamx = 0.17f ;
ArrayList<Size> sizelistSuitable = new ArrayList<Size>(); //
int widthSuitable = 0;
int heightSuitable = 0;
int whRatioSuitable = 0;
ArrayList<Size> sizelistNoSuitable = new ArrayList<Size>(); //
ArrayList<Integer> sizelistNoSuitableRatio = new ArrayList<Integer>(); //
ArrayList<Size> sizelistNoSuitable_1 = new ArrayList<Size>(); //
for(Size s : outputSizes) {
float sceenRatio = (float)(s.getWidth() * s.getHeight())/ screenTotalPixel ;
if (sceenRatio >= rangemin && sceenRatio <= rangeamx) { //合适的大小
int aspect_ratio = s.getWidth() *10 /s.getHeight() ;
switch (aspect_ratio){
case ratio4_3:
case ratio3_2:
case ratio16_10:
case ratio16_9:
widthSuitable = s.getWidth(); heightSuitable = s.getHeight(); whRatioSuitable = aspect_ratio ;
break;
default:
sizelistSuitable.add(s);
break;
}
if (widthSuitable > 0) {
break;
}
}else{
int aspect_ratio = s.getWidth() *10 /s.getHeight() ;
switch (aspect_ratio){
case ratio4_3:
case ratio3_2:
case ratio16_10:
case ratio16_9:
sizelistNoSuitable.add(s);
sizelistNoSuitableRatio.add(aspect_ratio);
break;
default:
sizelistNoSuitable_1.add(s);
break;
}
}
}
//最高优先级
if(widthSuitable > 0){ //有合适的大小 宽高比 已知
mPreviewSize = new Size(widthSuitable,heightSuitable);
mLayoutSize = getSuitableLayoutSize(mPreviewSize,whRatioSuitable,screenW,screenH);
updateFloatingWindowLayout(mLayoutSize.getWidth(),mLayoutSize.getHeight());
}else if(sizelistNoSuitable.size() > 0){//
int average = (int)(sizelistNoSuitable.size()/2); //平均值 偶数个 取大的
Size tSize = sizelistNoSuitable.get(average);
int tRatio = sizelistNoSuitableRatio.get(average);
mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());
mLayoutSize = getSuitableLayoutSize(mPreviewSize,tRatio,screenW,screenH);
updateFloatingWindowLayout(mLayoutSize.getWidth(),mLayoutSize.getHeight());
}else if(sizelistSuitable.size() > 0){ //没找到匹配的比例,图像拉伸吧
int average = (int)(sizelistSuitable.size()/2); //平均值 偶数个 取大的
Size tSize = sizelistSuitable.get(average);
mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());
}else if(sizelistNoSuitable_1.size() > 0){ //没找到匹配的比例,图像拉伸吧
int average = (int)(sizelistNoSuitable_1.size()/2); //平均值 偶数个 取大的
Size tSize = sizelistNoSuitable_1.get(average);
mPreviewSize = new Size(tSize.getWidth(),tSize.getHeight());
}
//不可能走到这里的 只要 outputSizes 有数据
return false ;
}
//得到合适的FPS
private android.util.Range<Integer> getSuitableFpsByDefaultFps(android.util.Range<Integer>[] ranges){
final int default_fps_diff = DEFAULT_CAMERA_FPS/4; //1/4 范围
final int default_fps_min = Math.max(DEFAULT_CAMERA_FPS - default_fps_diff,20);
final int default_fps_max = Math.min(DEFAULT_CAMERA_FPS + default_fps_diff,40);
ArrayList<android.util.Range<Integer>> sizelist1 = new ArrayList<android.util.Range<Integer>>();
ArrayList<android.util.Range<Integer>> sizelist2 = new ArrayList<android.util.Range<Integer>>(); //恒定帧
for(android.util.Range<Integer> r : ranges){
if(r.getLower()>= default_fps_min && r.getUpper() <= default_fps_max){
sizelist1.add(r);
if(r.getLower() == r.getUpper()){
sizelist2.add(r);
if(r.getLower() == DEFAULT_CAMERA_FPS){ //有默认帧,直接返回
return r ;
}
}
}
}
//从低往高 0 帧率最低
final int fps2 = sizelist2.size() ;
if(fps2 > 0){
return sizelist2.get(fps2-1); //取高帧
}else if(sizelist1.size() >0){
int avarge = sizelist1.size()/2;
return sizelist1.get(avarge);
}
都找不到就取个中间值
int avarge = ranges.length/2;
return ranges[avarge];
}
//检测 设置 相关属性
private boolean setAndCheckLinkProp(CameraCharacteristics cameraCharacteristics){
int supportLevel = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if(!isHardwareLevelSupported(supportLevel,CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)){ //希望支持
Toast.makeText(this,"当前相机硬件不支持",Toast.LENGTH_SHORT).show();
return false ;
}
//判断是否支持闪光灯
Boolean available = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size [] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
// Size largest = Collections.max(Arrays.asList(outputSizes), new CompareSizeByArea()); //得到最大尺寸
// 获取预览画面的尺寸 //[0] 最大 从高到底
if(!(outputSizes!= null && outputSizes.length > 0)){
return false;
}
boolean bFound = false ;
for(Size s :outputSizes){
if(s.getWidth() == PREVIEW_WIDTH && s.getHeight() == PREVIEW_HEIGHT){
bFound = true ; //
break;
}
}
if(bFound){
mPreviewSize = new Size(PREVIEW_WIDTH,PREVIEW_HEIGHT);
}else {
//这里取中间值不怎么好,最好得是找一个接近默认值比的宽高度,有旦继续优化
// int average = (int)(outputSizes.length/2); //平均值 偶数个 取大的
// mPreviewSize = outputSizes[average] ;//map.getOutputSizes(SurfaceTexture.class)[0]; //0 为最大尺寸, 感觉不合理,所以取平均值
// mPreviewSize = getSuitablePreviewSize(outputSizes);
// if(mPreviewSize == null) return false ;
getSuitablePreviewSize(outputSizes);
// updateFloatingWindowLayout(mPreviewSize.getWidth(),mPreviewSize.getHeight()); //重新更新下
}
//帧率
//Key[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
android.util.Range<Integer>[] ranges = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if(ranges != null && ranges.length > 0){
//取中间值 从低往高 0 帧率最低
// int average = (int)(ranges.length/2); //平均值 偶数个 取大的
// mAverageFPS = ranges[average]; //中间值
mAverageFPS = getSuitableFpsByDefaultFps(ranges);
}
return true;
}
//得到默认选择的相机ID(String 类型)
private String getDefaultCameraID(){
return cameraIdFront ;//
}
//切换相机
private void swapDefaultCameraID(){
if(cameraIdFront.equals("1")){ //前置
cameraIdFront = "0" ; //后置相机
}else{
cameraIdFront = "1" ; 前置相机
}
}
//初始化相机
private boolean initCameraInfo(String strCameraID) {
if(null == strCameraID) return false ;
if(strCameraID.isEmpty()) return false ;
if(!getScreenDisplayInfo()) return false ; //得到屏幕信息
mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
//if(!checkCameraExistByCameraID(strCameraID)) return false ; //检测相机是否存在 //没必要检测,都指定ID了
cameraCharacteristicsFront = getCameraCharacteristicsByCameraID(strCameraID); //
if (cameraCharacteristicsFront == null) return false;
return setAndCheckLinkProp(cameraCharacteristicsFront);
}
// 对于Camera2采集系统来说,每个摄像头都有一个支持等级: 从高到底为 越高越好
// INFO_SUPPORTED_HARDWARE_LEVEL_3 支持YUV再处理和原始数据采集功能,并且具备先进的功能。
// INFO_SUPPORTED_HARDWARE_LEVEL_FULL支持先进的摄像头功能。
// INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED向后兼容模式,底层等同于Camera1的实现。
// INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY 随机赠送的功能支持,支持性不足。
// 总的来说如果摄像头等级是LEVEL_3和LEVEL_FULL才建议使用Camera2进行采集,否则推荐采用兼容性更好的Camera1进行视频采集。
//判断是否支持要求的等级
// Returns true if the device supports the required hardware level, or better.
boolean isHardwareLevelSupported(int supportLevel , int requiredLevel) {
final int[] sortedHwLevels = {
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
};
int deviceLevel = supportLevel ; CameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (requiredLevel == deviceLevel) {
return true;
}
for (int sortedlevel : sortedHwLevels) {
if (sortedlevel == requiredLevel) {
return true;
} else if (sortedlevel == deviceLevel) {
return false;
}
}
return false; // Should never reach here
}
//初始化
private void initImageAvailableListener(){
imageAvailableListener = new ImageReader.OnImageAvailableListener(){
@Override
public void onImageAvailable(ImageReader var1){
Image image = var1.acquireLatestImage() ;
if(image== null){
return;
}
//得到图片 转成nv21格式
byte [] date = YUV_420_888toNV21(image);
// ByteBuffer buffer = image.getPlanes()[0].getBuffer();
// int length = buffer.remaining();
// var bytes = ByteArray(length);
// 转成 Bitmap
// ByteBuffer buffer = image.getPlanes()[0].getBuffer();
// byte[] b = new byte[buffer.capacity()];
// buffer.get(b, 0 , b.length);
// Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
image.close();
}
};
}
private void createCameraDeviceCallback(){
mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
mCameraDevice = null;
}
};
}
public void openCamera(){
mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
startBackgroundThread(); //开启
createCameraDeviceCallback() ; //相机状态监听
createImageReader(); //图像解析
try {
mCameraManager.openCamera(getDefaultCameraID(), mStateCallback, getBackgroundHandler()); //"0" 后置 "1" 前置 // handler null 主线程处理 否 创建个独立线程区处理
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//创建分析
private void createImageReader() {
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
}
//mPreviewSize.getWidth()
mImageReader = ImageReader.newInstance(getPreviewWidth(), getPreviewHeight(),
ImageFormat.YUV_420_888, RECV_MAX_IMAGES);
// imageReader = ImageReader.newInstance(1280, 720, ImageFormat.JPEG, 2)
mImageReader.setOnImageAvailableListener(imageAvailableListener, getBackgroundHandler());
}
//创建预览会话
private void createCameraPreviewSession() {
try {
final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
if(isOpenFloatingWindow()){ //是否开启悬浮窗口
previewRequestBuilder.addTarget(mPreviewSurface);
}
previewRequestBuilder.addTarget(mImageReader.getSurface());
//这几句可以放到 // 放到设置预览时连续捕获图像数据前 也可以
// 设置自动对焦模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
if(isFlashSupported()){ //如果支持就开启
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
//设置帧率
if(mAverageFPS!=null){
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mAverageFPS);
}
// 开始显示相机预览
mPreviewRequest = previewRequestBuilder.build();
//
// width 和 height 与 surfaceview 的相同
//ImageReader 用于接收处理(拍照、录像)的相机流
//SurfaceView 用于接收预览的相机流
// ImageReader mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
List<Surface> surfaces = new ArrayList<Surface>();
if(isOpenFloatingWindow()) { //是否开启悬浮窗口
surfaces.add(mPreviewSurface);
}
surfaces.add(mImageReader.getSurface());
// surfaces.add(surfaceView.getHolder().getSurface());
// surfaces.add(mImageReader.getSurface());
// Arrays.asList(surface)
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
try {
//这几句可以放到 // 放到设置预览时连续捕获图像数据 也可以
// 设置自动对焦模式
// previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// // 设置自动曝光模式
// if(isFlashSupported()){ //如果支持就开启
// previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// }
// //设置帧率
// if(mAverageFPS!=null){
// previewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mAverageFPS);
// }
// // 开始显示相机预览
// mPreviewRequest = previewRequestBuilder.build();
//
// 设置预览时连续捕获图像数据
//listener 这里不是拍照,预览的无需回调,拍照就需要了
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Log.e("MainActivity", "onConfigureFailed");
}
}, getBackgroundHandler());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
数据格式处理//
//数据转化 抄别人的 //
//Planar格式(P)的处理
private static ByteBuffer getuvBufferWithoutPaddingP(ByteBuffer uBuffer, ByteBuffer vBuffer, int width, int height, int rowStride, int pixelStride){
int pos = 0;
byte []byteArray = new byte[height*width/2];
for (int row=0; row<height/2; row++) {
for (int col=0; col<width/2; col++) {
int vuPos = col*pixelStride + row*rowStride;
byteArray[pos++] = vBuffer.get(vuPos);
byteArray[pos++] = uBuffer.get(vuPos);
}
}
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
//Semi-Planar格式(SP)的处理和y通道的数据
private static ByteBuffer getBufferWithoutPadding(ByteBuffer buffer, int width, int rowStride, int times,boolean isVbuffer){
if(width == rowStride) return buffer; //没有buffer,不用处理。
int bufferPos = buffer.position();
int cap = buffer.capacity();
byte []byteArray = new byte[times*width];
int pos = 0;
//对于y平面,要逐行赋值的次数就是height次。对于uv交替的平面,赋值的次数是height/2次
for (int i=0;i<times;i++) {
buffer.position(bufferPos);
//part 1.1 对于u,v通道,会缺失最后一个像u值或者v值,因此需要特殊处理,否则会crash
if(isVbuffer && i==times-1){
width = width -1;
}
buffer.get(byteArray, pos, width);
bufferPos+= rowStride;
pos = pos+width;
}
//nv21数组转成buffer并返回
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
private static byte[] YUV_420_888toNV21(Image image) {
int width = image.getWidth();
int height = image.getHeight();
ByteBuffer yBuffer = getBufferWithoutPadding(image.getPlanes()[0].getBuffer(), image.getWidth(), image.getPlanes()[0].getRowStride(),image.getHeight(),false);
ByteBuffer vBuffer;
//part1 获得真正的消除padding的ybuffer和ubuffer。需要对P格式和SP格式做不同的处理。如果是P格式的话只能逐像素去做,性能会降低。
if(image.getPlanes()[2].getPixelStride()==1){ //如果为true,说明是P格式。
vBuffer = getuvBufferWithoutPaddingP(image.getPlanes()[1].getBuffer(), image.getPlanes()[2].getBuffer(),
width,height,image.getPlanes()[1].getRowStride(),image.getPlanes()[1].getPixelStride());
}else{
vBuffer = getBufferWithoutPadding(image.getPlanes()[2].getBuffer(), image.getWidth(), image.getPlanes()[2].getRowStride(),image.getHeight()/2,true);
}
//part2 将y数据和uv的交替数据(除去最后一个v值)赋值给nv21
int ySize = yBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21;
int byteSize = width*height*3/2;
nv21 = new byte[byteSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
//part3 最后一个像素值的u值是缺失的,因此需要从u平面取一下。
ByteBuffer uPlane = image.getPlanes()[1].getBuffer();
byte lastValue = uPlane.get(uPlane.capacity() - 1);
nv21[byteSize - 1] = lastValue;
return nv21;
}
//悬浮窗口 移动 更新位置
private class FloatingOnTouchListener implements View.OnTouchListener {
private int x;
private int y;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX - x;
int movedY = nowY - y;
x = nowX;
y = nowY;
layoutParams.x = layoutParams.x + movedX;
layoutParams.y = layoutParams.y + movedY;
windowManager.updateViewLayout(view, layoutParams);
break;
default:
break;
}
return true;
}
}
//
//权限
//
//
//
//硬件加速
// android:hardwareAccelerated="true"
//使用 useAndroidX
//android.useAndroidX=true
//增加依赖
//implementation 'androidx.core:core:1.2.0' 跟下面3句有关系,不增加,下面的找不到
// import androidx.annotation.NonNull;
//import androidx.core.app.ActivityCompat;
//import androidx.core.content.ContextCompat;
///
// unity 导出 Plugin Version(3.6.0) Gradle Version(6.1.1) 这里用
// compileSdkVersion 33
// buildToolsVersion '33.0.0'
// 不支持 所以需要提升下
// Android Gradle Plugin Version
//3.6.0 ----> 4.1.1
// Gradle Version
//6.1.1 ----> 6.9
//
//启动过程
//1: 申请权限,得到相机与屏幕 信息 多出相依处理
//applyForPermission() //放到 onCreate 里就OK了
//有权限 dosomething
//2:dosomething 里 初始化相机
//createFloatingWindowLayout() 初始化各种监听 等
//3: 点击事件 或其他地方调用
//addFloatingWindowView 触发
// TextureView.SurfaceTextureListener() 的
// public void onSurfaceTextureAvailable 从而 启动相机
//4: 启动相机
//openCamera 在 mCameraManager.openCamera 前 回调函数都设置好
//createCameraDeviceCallback() ; //相机状态监听回调
// createImageReader(); //图像解析
//触发 createCameraPreviewSession
//5 创建预览会话
// 预览 解析 加入会话申请中
}
调用部分
函数说明
getSuitablePreviewSize
找到合适的宽高比 尺寸
当前 4种(4:3 3:2 16:10 16:9) ,预览显示框TextureView 会根据选定相机输出比 重置自己的尺寸
找不到预定的比,用默认的图像会拉伸
这里 希望找到 屏幕 的 1/9 到 1/6 相机输出图像 ,如果屏幕像素比320*240还小会出问题,
屏幕不会这么小的吧
宽高比 这里放到10倍了
java 接口代码
package com.unity3d.player;
public class JNIUtils {
static {
System.loadLibrary("ImageFormatConversion");
}
public native static boolean yuv420888ToNv21(int width, int height, int yRowStride, int uRowStride, int vRowStride, int pixelStride, byte[] yPlane, byte[] uPlane, byte[] vPlane, byte[] nv21);
}
com_unity3d_player_JNIUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_unity3d_player_JNIUtils */
#ifndef _Included_com_unity3d_player_JNIUtils
#define _Included_com_unity3d_player_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_unity3d_player_JNIUtils
* Method: yuv420888ToNv21
* Signature: (IIIIII[B[B[B[B)Z
*/
JNIEXPORT jboolean JNICALL Java_com_unity3d_player_JNIUtils_yuv420888ToNv21
(JNIEnv *, jclass, jint, jint, jint, jint, jint, jint, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif
cpp
```cpp
#include
#include
#include
#include
using namespace std ;
#include "com_unity3d_player_JNIUtils.h"
#include
#include
#define TAG "ImageFormatConversion"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, TAG, FORMAT, ##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, TAG, FORMAT, ##__VA_ARGS__);
void* aligned_malloc(size_t required_bytes, size_t alignment);
void aligned_free(void *p2);
//NEON 指令
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jboolean
JNICALL Java_com_unity3d_player_JNIUtils_yuv420888ToNv21
(JNIEnv *env, jclass jc, jint w, jint h, jint yRowStride, jint uRowStride, jint vRowStride,
jint pixelStride, jbyteArray jy, jbyteArray ju, jbyteArray jv, jbyteArray jnv) {
jbyte *ySrcPtr = (jbyte*)env->GetByteArrayElements(jy, 0);
jbyte *uSrcPtr = (jbyte*)env->GetByteArrayElements(ju, 0);
jbyte *vSrcPtr = (jbyte*)env->GetByteArrayElements(jv, 0);
jsize oldsize = env->GetArrayLength(jy);
// jbyte *uSrcPtr = (jbyte *) ju;
// jbyte *vSrcPtr = (jbyte *) jv;
const int width = w;
const int height = h;
//首先nv21图像数组的长度一定是长×宽×3/2,其中y数据的大小是长×宽(因为每个像素都有一个y值),接着所有像素的u,v值总共有(长×宽×1/2)个
// unsigned char *buv = (unsigned char *) malloc(width * height * 3 / 2);
const int len = width * height * 3 / 2;
// jbyteArray array = env->NewByteArray(len);
// const int len = env->GetArrayLength(jnv);
jbyteArray array = jnv ;//env->NewByteArray(len); //jnv ;
//Y
int row = 0;
for(row = 0; row < height; row++){ //307200 //arry len 460800
env->SetByteArrayRegion(array, row * width, width, ySrcPtr + row * yRowStride);
}
const int curpos = row * width ;
constexpr int kStride = 8;
const size_t nGroups = width / kStride;
// LOGE("#666666##")
//uv
if (pixelStride == 1) { //YUV_P
// LOGE("#77777##")
int8_t *line = (int8_t*)aligned_malloc(width, 64);
for(row = 0; row < height / 2; row ++) {
size_t vrowOff = row * vRowStride;
size_t urowOff = row * uRowStride;
for(int g = 0; g < nGroups / 2; g++){
size_t colOff = g * kStride;
int8x8_t v0 = vld1_s8(vSrcPtr + vrowOff + colOff);
int8x8_t v1 = vld1_s8(uSrcPtr + urowOff + colOff);
int8x8x2_t vz = vzip_s8(v0, v1);
vst1_s8(line + colOff, vz.val[0]);
vst1_s8(line + colOff + kStride, vz.val[1]);
}
env->SetByteArrayRegion(array, curpos+row * width, width, line);
}
// LOGE("#99999##")
aligned_free(line);
} else { //YUV_SP 交替的
// LOGE("#88888##")
int8_t *line = (int8_t*)aligned_malloc(width, 64);
int8_t *mask = (int8_t*)aligned_malloc(kStride, 64);
memset(mask, 0, kStride);
for(int i=0; i < kStride / 2; i++) {
mask[i * 2] = -1;
}
int8x8_t vm = vld1_s8(mask);
for(int row = 0; row < height / 2; row ++){
size_t vrowOff = row * vRowStride;
size_t urowOff = row * uRowStride;
for(int g = 0; g < nGroups; g++) {
size_t colOff = g * kStride;
int8x8_t v0 = vld1_s8(vSrcPtr + vrowOff + colOff);
int8x8_t v1 = vld1_s8(uSrcPtr + urowOff + colOff);
int8x8_t a0 = vand_s8(v0, vm);
int16x4_t b1 = vreinterpret_s16_s8(vand_s8(v1, vm));
int8x8_t a1 = vreinterpret_s8_s16(vshl_n_s16(b1, 8));
int8x8_t r = vorr_s8(a0, a1);
vst1_s8(line + colOff, r);
}
env->SetByteArrayRegion(array,curpos+ row * width, width, line);
}
// LOGE("#99999##")
aligned_free(mask);
aligned_free(line);
}
// LOGE("#101010##")
// jbyte *tcopy= (jbyte*)env->GetByteArrayElements(array, 0);
// env->SetByteArrayRegion(jnv,0, len, tcopy);
// env->DeleteLocalRef(array); //释放
// LOGE("#AAAAAAAA##")
return true;
}
#ifdef __cplusplus
}
#endif
void* aligned_malloc(size_t required_bytes, size_t alignment)
{
int offset = alignment - 1 + sizeof(void*);
void* p1 = (void*)malloc(required_bytes + offset);
if (p1 == NULL)
return NULL;
void** p2 = (void**)( ( (size_t)p1 + offset ) & ~(alignment - 1) );
p2[-1] = p1;
return p2;
}
void aligned_free(void *p2)
{
void* p1 = ((void**)p2)[-1];
free(p1);
}
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
apply plugin: 'com.android.library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//by eh add
implementation 'androidx.core:core:1.2.0'
1.1.0-beta01
//下面2句没加一样跑,不知道在上面那里是否已经包含了
def camera2_version = "1.2.0"
implementation "androidx.camera:camera-core:${camera2_version}"
implementation "androidx.camera:camera-camera2:${camera2_version}"
}
android {
compileSdkVersion 33
buildToolsVersion '33.0.0'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 23
targetSdkVersion 30
ndk {
abiFilters 'arm64-v8a'
}
versionCode 1
versionName '1.0'
consumerProguardFiles 'proguard-unity.txt'
//cmake build//
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
abiFilters 'arm64-v8a'
}
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
packagingOptions {
doNotStrip '*/arm64-v8a/*.so'
}
//cmake build//
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
/
}
def getSdkDir() {
Properties local = new Properties()
local.load(new FileInputStream("${rootDir}/local.properties"))
return local.getProperty('sdk.dir')
}
def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) {
exec {
commandLine(workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/netcoreapp3.1/il2cpp.exe",
"--compile-cpp",
"--libil2cpp-static",
"--platform=Android",
"--architecture=" + architecture,
"--configuration=" + configuration,
"--outputpath=" + workingDir + targetDirectory + abi + "/libil2cpp.so",
"--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/external/bdwgc/include",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/include",
"--tool-chain-path=" + android.ndkDirectory,
"--map-file-parser=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/MapFileParser/MapFileParser.exe",
"--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput",
"--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi,
"--dotnetprofile=unityaot")
environment "ANDROID_SDK_ROOT", getSdkDir()
}
delete workingDir + targetDirectory + abi + "/libil2cpp.sym.so"
ant.move(file: workingDir + targetDirectory + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
}
android {
task BuildIl2CppTask {
doLast {
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARM64', 'arm64-v8a', 'Release');
}
}
afterEvaluate {
if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders'))
project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders'))
project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
}
sourceSets {
main {
jni.srcDirs = ["src/main/Il2CppOutputProject"]
}
}
}
CMakelist.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
add_library(ImageFormatConversion
SHARED
src/main/jni/com_unity3d_player_JNIUtils.cpp
)
find_library(log-lib log)
find_library(dl-lib dl)
find_library(android-lib android)
target_link_libraries(ImageFormatConversion ${log-lib} ${dl-lib} ${android-lib})
3 测试代码及结果
测试代码
static int tcount = 0;
static long tesms = 0;
// .............................................
long starttime = System.currentTimeMillis() ;
{/* c++ so 测试
int w = image.getWidth();
int h = image.getHeight();
Image.Plane y = image.getPlanes()[0];
Image.Plane u = image.getPlanes()[1];
Image.Plane v = image.getPlanes()[2];
byte[] buf = new byte[w * h * 3 / 2];
int pixelStride = v.getPixelStride(); // image.getPlanes()[2].getPixelStride()
int ylen = y.getBuffer().remaining(); //可用长度
byte[] by = new byte[ylen];
y.getBuffer().get(by);
int ulen = u.getBuffer().remaining();
byte[] bu = new byte[ulen];
u.getBuffer().get(bu);
int vlen = v.getBuffer().remaining();
byte[] bv = new byte[vlen];
v.getBuffer().get(bv);
if (!JNIUtils.yuv420888ToNv21(w, h, y.getRowStride(), u.getRowStride(), v.getRowStride(), pixelStride, by, bu, bv, buf)) {
Log.e(TAG, "FAIL");
}
*/
{ //java 测试
byte [] date = YUV_420_888toNV21(image);
}
}
long end = System.currentTimeMillis() ;
tesms += end-starttime ;
tcount++ ;
if(tcount < 100){
}else{
Log.e(TAG,String.valueOf(tcount)+":"+String.valueOf(tesms));
tcount = 0;
tesms = 0;
}
结果 出乎意料,不知道是不是so为DEBUG版本 c++ 版这么差
4 demo工程
下载地址
觉得有用 ,点个赞,加个关注