parent
2f35000e84
commit
d80cbd80e8
@ -1,122 +0,0 @@ |
|||||||
package com.frank.live.Push; |
|
||||||
|
|
||||||
import android.graphics.ImageFormat; |
|
||||||
import android.hardware.Camera; |
|
||||||
import android.view.SurfaceHolder; |
|
||||||
|
|
||||||
import com.frank.live.LiveUtil; |
|
||||||
import com.frank.live.param.VideoParam; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* 视频推流 |
|
||||||
* Created by frank on 2018/1/28. |
|
||||||
*/ |
|
||||||
|
|
||||||
public class VideoPusher extends Pusher implements SurfaceHolder.Callback, Camera.PreviewCallback { |
|
||||||
|
|
||||||
private SurfaceHolder surfaceHolder; |
|
||||||
private VideoParam videoParam; |
|
||||||
private Camera camera; |
|
||||||
private boolean isPushing; |
|
||||||
private byte[] previewBuffer; |
|
||||||
private LiveUtil liveUtil; |
|
||||||
|
|
||||||
VideoPusher(SurfaceHolder surfaceHolder, VideoParam videoParam, LiveUtil liveUtil){ |
|
||||||
this.surfaceHolder = surfaceHolder; |
|
||||||
this.videoParam = videoParam; |
|
||||||
this.liveUtil = liveUtil; |
|
||||||
surfaceHolder.addCallback(this); |
|
||||||
liveUtil.setVideoParams(videoParam.getWidth(), videoParam.getHeight(), |
|
||||||
videoParam.getBitRate(), videoParam.getFrameRate()); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public void startPush() { |
|
||||||
isPushing = true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void stopPush() { |
|
||||||
isPushing = false; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void release() { |
|
||||||
stopPush(); |
|
||||||
stopPreview(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void surfaceCreated(SurfaceHolder holder) { |
|
||||||
startPreview(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) { |
|
||||||
stopPreview(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 开始预览 |
|
||||||
*/ |
|
||||||
private void startPreview() { |
|
||||||
try { |
|
||||||
camera = Camera.open(videoParam.getCameraId()); |
|
||||||
Camera.Parameters parameters = camera.getParameters(); |
|
||||||
parameters.setPreviewFormat(ImageFormat.NV21); |
|
||||||
parameters.setPictureSize(videoParam.getWidth(), videoParam.getHeight()); |
|
||||||
camera.setParameters(parameters); |
|
||||||
camera.setDisplayOrientation(0);//竖屏是90°
|
|
||||||
camera.setPreviewDisplay(surfaceHolder); |
|
||||||
camera.startPreview(); |
|
||||||
previewBuffer = new byte[videoParam.getWidth() * videoParam.getHeight() * 4]; |
|
||||||
camera.addCallbackBuffer(previewBuffer); |
|
||||||
camera.setPreviewCallbackWithBuffer(this); |
|
||||||
} catch (IOException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 停止预览 |
|
||||||
*/ |
|
||||||
private void stopPreview() { |
|
||||||
if(camera != null){ |
|
||||||
camera.stopPreview(); |
|
||||||
camera.setPreviewCallback(null); |
|
||||||
camera.release(); |
|
||||||
camera = null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 切换摄像头 |
|
||||||
*/ |
|
||||||
void switchCamera(){ |
|
||||||
|
|
||||||
if(videoParam.getCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK){ |
|
||||||
videoParam.setCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT); |
|
||||||
}else { |
|
||||||
videoParam.setCameraId(Camera.CameraInfo.CAMERA_FACING_BACK); |
|
||||||
} |
|
||||||
//重新开始推流
|
|
||||||
stopPreview(); |
|
||||||
startPreview(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onPreviewFrame(byte[] data, Camera camera) { |
|
||||||
camera.addCallbackBuffer(previewBuffer); |
|
||||||
if(isPushing){ |
|
||||||
liveUtil.pushVideoData(data); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,155 @@ |
|||||||
|
package com.frank.live.Push; |
||||||
|
|
||||||
|
import android.app.Activity; |
||||||
|
import android.content.Context; |
||||||
|
import android.graphics.Point; |
||||||
|
import android.graphics.SurfaceTexture; |
||||||
|
import android.util.Log; |
||||||
|
import android.util.Size; |
||||||
|
import android.view.TextureView; |
||||||
|
|
||||||
|
import com.frank.live.LiveUtil; |
||||||
|
import com.frank.live.camera2.Camera2Helper; |
||||||
|
import com.frank.live.camera2.Camera2Listener; |
||||||
|
import com.frank.live.param.VideoParam; |
||||||
|
|
||||||
|
/** |
||||||
|
* 视频推流:使用Camera2 |
||||||
|
* Created by frank on 2019/12/18. |
||||||
|
*/ |
||||||
|
|
||||||
|
public class VideoPusherNew extends Pusher implements TextureView.SurfaceTextureListener, Camera2Listener { |
||||||
|
private final static String TAG = VideoPusherNew.class.getSimpleName(); |
||||||
|
|
||||||
|
private VideoParam mVideoParam; |
||||||
|
private Camera2Helper camera2Helper; |
||||||
|
private boolean isPushing; |
||||||
|
private LiveUtil mLiveUtil; |
||||||
|
private TextureView mTextureView; |
||||||
|
private Context mContext; |
||||||
|
|
||||||
|
VideoPusherNew(TextureView textureView, VideoParam videoParam, LiveUtil liveUtil, Context context) { |
||||||
|
this.mTextureView = textureView; |
||||||
|
this.mVideoParam = videoParam; |
||||||
|
this.mLiveUtil = liveUtil; |
||||||
|
this.mContext = context; |
||||||
|
mTextureView.setSurfaceTextureListener(this); |
||||||
|
liveUtil.setVideoParams(videoParam.getWidth(), videoParam.getHeight(), |
||||||
|
videoParam.getBitRate(), videoParam.getFrameRate()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void startPush() { |
||||||
|
isPushing = true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void stopPush() { |
||||||
|
isPushing = false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void release() { |
||||||
|
stopPush(); |
||||||
|
releasePreview(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 开始预览 |
||||||
|
*/ |
||||||
|
private void startPreview() { |
||||||
|
int rotateDegree = 0; |
||||||
|
if (mContext instanceof Activity) { |
||||||
|
rotateDegree = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation(); |
||||||
|
} |
||||||
|
Log.e(TAG, "preview width=" + mTextureView.getWidth() + "--height=" + mTextureView.getHeight()); |
||||||
|
camera2Helper = new Camera2Helper.Builder() |
||||||
|
.cameraListener(this) |
||||||
|
.maxPreviewSize(new Point(1080, 720)) |
||||||
|
.minPreviewSize(new Point(mVideoParam.getWidth(), mVideoParam.getHeight())) |
||||||
|
.specificCameraId(Camera2Helper.CAMERA_ID_BACK) |
||||||
|
.context(mContext.getApplicationContext()) |
||||||
|
.previewOn(mTextureView) |
||||||
|
// .previewViewSize(new Point(mTextureView.getWidth(), mTextureView.getHeight()))
|
||||||
|
.previewViewSize(new Point(mVideoParam.getWidth(), mVideoParam.getHeight())) |
||||||
|
.rotation(rotateDegree) |
||||||
|
.build(); |
||||||
|
camera2Helper.start(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 停止预览 |
||||||
|
*/ |
||||||
|
private void stopPreview() { |
||||||
|
if (camera2Helper != null) { |
||||||
|
camera2Helper.stop(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 释放资源 |
||||||
|
*/ |
||||||
|
private void releasePreview() { |
||||||
|
if (camera2Helper != null) { |
||||||
|
camera2Helper.stop(); |
||||||
|
camera2Helper.release(); |
||||||
|
camera2Helper = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 切换摄像头 |
||||||
|
*/ |
||||||
|
void switchCamera() { |
||||||
|
if (camera2Helper != null) { |
||||||
|
camera2Helper.switchCamera(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { |
||||||
|
Log.e(TAG, "onSurfaceTextureAvailable..."); |
||||||
|
startPreview(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { |
||||||
|
Log.e(TAG, "onSurfaceTextureDestroyed..."); |
||||||
|
stopPreview(); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onPreviewFrame(byte[] y, byte[] u, byte[] v) { |
||||||
|
if (isPushing && mLiveUtil != null) { |
||||||
|
mLiveUtil.pushVideoData(y, u, v); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraOpened(Size previewSize, int displayOrientation) { |
||||||
|
Log.e(TAG, "onCameraOpened previewSize=" + previewSize.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraClosed() { |
||||||
|
Log.e(TAG, "onCameraClosed"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onCameraError(Exception e) { |
||||||
|
Log.e(TAG, "onCameraError=" + e.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,643 @@ |
|||||||
|
package com.frank.live.camera2; |
||||||
|
|
||||||
|
import android.Manifest; |
||||||
|
import android.annotation.TargetApi; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.pm.PackageManager; |
||||||
|
import android.graphics.ImageFormat; |
||||||
|
import android.graphics.Matrix; |
||||||
|
import android.graphics.Point; |
||||||
|
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.Handler; |
||||||
|
import android.os.HandlerThread; |
||||||
|
import android.util.Log; |
||||||
|
import android.util.Size; |
||||||
|
import android.view.Surface; |
||||||
|
import android.view.TextureView; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.Semaphore; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
import java.util.concurrent.locks.ReentrantLock; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Camera2操作 |
||||||
|
* Created by frank on 2019/12/18. |
||||||
|
*/ |
||||||
|
@TargetApi(21) |
||||||
|
public class Camera2Helper { |
||||||
|
|
||||||
|
private static final String TAG = Camera2Helper.class.getSimpleName(); |
||||||
|
|
||||||
|
private Point maxPreviewSize; |
||||||
|
private Point minPreviewSize; |
||||||
|
|
||||||
|
public static final String CAMERA_ID_FRONT = "1"; |
||||||
|
public static final String CAMERA_ID_BACK = "0"; |
||||||
|
|
||||||
|
|
||||||
|
private String mCameraId; |
||||||
|
private String specificCameraId; |
||||||
|
private Camera2Listener camera2Listener; |
||||||
|
private TextureView mTextureView; |
||||||
|
private int rotation; |
||||||
|
private Point previewViewSize; |
||||||
|
private Point specificPreviewSize; |
||||||
|
private Context context; |
||||||
|
/** |
||||||
|
* A {@link CameraCaptureSession } for camera preview. |
||||||
|
*/ |
||||||
|
private CameraCaptureSession mCaptureSession; |
||||||
|
|
||||||
|
/** |
||||||
|
* A reference to the opened {@link CameraDevice}. |
||||||
|
*/ |
||||||
|
private CameraDevice mCameraDevice; |
||||||
|
|
||||||
|
private Size mPreviewSize; |
||||||
|
|
||||||
|
private Camera2Helper(Builder builder) { |
||||||
|
mTextureView = builder.previewDisplayView; |
||||||
|
specificCameraId = builder.specificCameraId; |
||||||
|
camera2Listener = builder.camera2Listener; |
||||||
|
rotation = builder.rotation; |
||||||
|
previewViewSize = builder.previewViewSize; |
||||||
|
specificPreviewSize = builder.previewSize; |
||||||
|
maxPreviewSize = builder.maxPreviewSize; |
||||||
|
minPreviewSize = builder.minPreviewSize; |
||||||
|
context = builder.context; |
||||||
|
} |
||||||
|
|
||||||
|
public void switchCamera() { |
||||||
|
if (CAMERA_ID_BACK.equals(mCameraId)) { |
||||||
|
specificCameraId = CAMERA_ID_FRONT; |
||||||
|
} else if (CAMERA_ID_FRONT.equals(mCameraId)) { |
||||||
|
specificCameraId = CAMERA_ID_BACK; |
||||||
|
} |
||||||
|
stop(); |
||||||
|
start(); |
||||||
|
} |
||||||
|
|
||||||
|
private int getCameraOri(int rotation, String cameraId) { |
||||||
|
int degrees = rotation * 90; |
||||||
|
switch (rotation) { |
||||||
|
case Surface.ROTATION_0: |
||||||
|
degrees = 0; |
||||||
|
break; |
||||||
|
case Surface.ROTATION_90: |
||||||
|
degrees = 90; |
||||||
|
break; |
||||||
|
case Surface.ROTATION_180: |
||||||
|
degrees = 180; |
||||||
|
break; |
||||||
|
case Surface.ROTATION_270: |
||||||
|
degrees = 270; |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
int result; |
||||||
|
if (CAMERA_ID_FRONT.equals(cameraId)) { |
||||||
|
result = (mSensorOrientation + degrees) % 360; |
||||||
|
result = (360 - result) % 360; |
||||||
|
} else { |
||||||
|
result = (mSensorOrientation - degrees + 360) % 360; |
||||||
|
} |
||||||
|
Log.i(TAG, "getCameraOri: " + rotation + " " + result + " " + mSensorOrientation); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
private final TextureView.SurfaceTextureListener mSurfaceTextureListener |
||||||
|
= new TextureView.SurfaceTextureListener() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { |
||||||
|
Log.i(TAG, "onSurfaceTextureAvailable: "); |
||||||
|
openCamera(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { |
||||||
|
Log.i(TAG, "onSurfaceTextureSizeChanged: "); |
||||||
|
configureTransform(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { |
||||||
|
Log.i(TAG, "onSurfaceTextureDestroyed: "); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture texture) { |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onOpened(@NonNull CameraDevice cameraDevice) { |
||||||
|
Log.i(TAG, "onOpened: "); |
||||||
|
// This method is called when the camera is opened. We start camera preview here.
|
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
mCameraDevice = cameraDevice; |
||||||
|
createCameraPreviewSession(); |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraOpened(mPreviewSize, getCameraOri(rotation, mCameraId)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onDisconnected(@NonNull CameraDevice cameraDevice) { |
||||||
|
Log.i(TAG, "onDisconnected: "); |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
cameraDevice.close(); |
||||||
|
mCameraDevice = null; |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraClosed(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onError(@NonNull CameraDevice cameraDevice, int error) { |
||||||
|
Log.i(TAG, "onError: "); |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
cameraDevice.close(); |
||||||
|
mCameraDevice = null; |
||||||
|
|
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(new Exception("error occurred, code is " + error)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
private CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { |
||||||
|
Log.i(TAG, "onConfigured: "); |
||||||
|
// The camera is already closed
|
||||||
|
if (null == mCameraDevice) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// When the session is ready, we start displaying the preview.
|
||||||
|
mCaptureSession = cameraCaptureSession; |
||||||
|
try { |
||||||
|
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), |
||||||
|
new CameraCaptureSession.CaptureCallback() { |
||||||
|
}, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onConfigureFailed( |
||||||
|
@NonNull CameraCaptureSession cameraCaptureSession) { |
||||||
|
Log.i(TAG, "onConfigureFailed: "); |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(new Exception("configureFailed")); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
/** |
||||||
|
* An additional thread for running tasks that shouldn't block the UI. |
||||||
|
*/ |
||||||
|
private HandlerThread mBackgroundThread; |
||||||
|
|
||||||
|
/** |
||||||
|
* A {@link Handler} for running tasks in the background. |
||||||
|
*/ |
||||||
|
private Handler mBackgroundHandler; |
||||||
|
|
||||||
|
private ImageReader mImageReader; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* {@link CaptureRequest.Builder} for the camera preview |
||||||
|
*/ |
||||||
|
private CaptureRequest.Builder mPreviewRequestBuilder; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A {@link Semaphore} to prevent the app from exiting before closing the camera. |
||||||
|
*/ |
||||||
|
private Semaphore mCameraOpenCloseLock = new Semaphore(1); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Orientation of the camera sensor |
||||||
|
*/ |
||||||
|
private int mSensorOrientation; |
||||||
|
|
||||||
|
private Size getBestSupportedSize(List<Size> sizes) { |
||||||
|
Size defaultSize = sizes.get(0); |
||||||
|
Size[] tempSizes = sizes.toArray(new Size[0]); |
||||||
|
Arrays.sort(tempSizes, new Comparator<Size>() { |
||||||
|
@Override |
||||||
|
public int compare(Size o1, Size o2) { |
||||||
|
if (o1.getWidth() > o2.getWidth()) { |
||||||
|
return -1; |
||||||
|
} else if (o1.getWidth() == o2.getWidth()) { |
||||||
|
return o1.getHeight() > o2.getHeight() ? -1 : 1; |
||||||
|
} else { |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
sizes = new ArrayList<>(Arrays.asList(tempSizes)); |
||||||
|
for (int i = sizes.size() - 1; i >= 0; i--) { |
||||||
|
if (maxPreviewSize != null) { |
||||||
|
if (sizes.get(i).getWidth() > maxPreviewSize.x || sizes.get(i).getHeight() > maxPreviewSize.y) { |
||||||
|
sizes.remove(i); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
if (minPreviewSize != null) { |
||||||
|
if (sizes.get(i).getWidth() < minPreviewSize.x || sizes.get(i).getHeight() < minPreviewSize.y) { |
||||||
|
sizes.remove(i); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (sizes.size() == 0) { |
||||||
|
String msg = "can not find suitable previewSize, now using default"; |
||||||
|
if (camera2Listener != null) { |
||||||
|
Log.e(TAG, msg); |
||||||
|
camera2Listener.onCameraError(new Exception(msg)); |
||||||
|
} |
||||||
|
return defaultSize; |
||||||
|
} |
||||||
|
Size bestSize = sizes.get(0); |
||||||
|
float previewViewRatio; |
||||||
|
if (previewViewSize != null) { |
||||||
|
previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y; |
||||||
|
} else { |
||||||
|
previewViewRatio = (float) bestSize.getWidth() / (float) bestSize.getHeight(); |
||||||
|
} |
||||||
|
|
||||||
|
if (previewViewRatio > 1) { |
||||||
|
previewViewRatio = 1 / previewViewRatio; |
||||||
|
} |
||||||
|
|
||||||
|
for (Size s : sizes) { |
||||||
|
if (specificPreviewSize != null && specificPreviewSize.x == s.getWidth() && specificPreviewSize.y == s.getHeight()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) { |
||||||
|
bestSize = s; |
||||||
|
} |
||||||
|
} |
||||||
|
return bestSize; |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void start() { |
||||||
|
if (mCameraDevice != null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
startBackgroundThread(); |
||||||
|
|
||||||
|
// When the screen is turned off and turned back on, the SurfaceTexture is already
|
||||||
|
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
|
||||||
|
// a camera and start preview from here (otherwise, we wait until the surface is ready in
|
||||||
|
// the SurfaceTextureListener).
|
||||||
|
if (mTextureView.isAvailable()) { |
||||||
|
openCamera(); |
||||||
|
} else { |
||||||
|
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void stop() { |
||||||
|
if (mCameraDevice == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
closeCamera(); |
||||||
|
stopBackgroundThread(); |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
stop(); |
||||||
|
mTextureView = null; |
||||||
|
camera2Listener = null; |
||||||
|
context = null; |
||||||
|
} |
||||||
|
|
||||||
|
private void setUpCameraOutputs(CameraManager cameraManager) { |
||||||
|
try { |
||||||
|
if (configCameraParams(cameraManager, specificCameraId)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (String cameraId : cameraManager.getCameraIdList()) { |
||||||
|
if (configCameraParams(cameraManager, cameraId)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} catch (NullPointerException e) { |
||||||
|
// Currently an NPE is thrown when the Camera2API is used but not supported on the
|
||||||
|
// device this code runs.
|
||||||
|
|
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean configCameraParams(CameraManager manager, String cameraId) throws CameraAccessException { |
||||||
|
CameraCharacteristics characteristics |
||||||
|
= manager.getCameraCharacteristics(cameraId); |
||||||
|
|
||||||
|
StreamConfigurationMap map = characteristics.get( |
||||||
|
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); |
||||||
|
if (map == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
mPreviewSize = getBestSupportedSize(new ArrayList<>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class)))); |
||||||
|
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), |
||||||
|
ImageFormat.YUV_420_888, 2); |
||||||
|
mImageReader.setOnImageAvailableListener( |
||||||
|
new OnImageAvailableListenerImpl(), mBackgroundHandler); |
||||||
|
|
||||||
|
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); |
||||||
|
mCameraId = cameraId; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void openCamera() { |
||||||
|
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); |
||||||
|
setUpCameraOutputs(cameraManager); |
||||||
|
configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); |
||||||
|
try { |
||||||
|
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { |
||||||
|
throw new RuntimeException("Time out waiting to lock camera opening."); |
||||||
|
} |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M |
||||||
|
&& context.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
cameraManager.openCamera(mCameraId, mDeviceStateCallback, mBackgroundHandler); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(e); |
||||||
|
} |
||||||
|
} catch (InterruptedException e) { |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Closes the current {@link CameraDevice}. |
||||||
|
*/ |
||||||
|
private void closeCamera() { |
||||||
|
try { |
||||||
|
mCameraOpenCloseLock.acquire(); |
||||||
|
if (null != mCaptureSession) { |
||||||
|
mCaptureSession.close(); |
||||||
|
mCaptureSession = null; |
||||||
|
} |
||||||
|
if (null != mCameraDevice) { |
||||||
|
mCameraDevice.close(); |
||||||
|
mCameraDevice = null; |
||||||
|
} |
||||||
|
if (null != mImageReader) { |
||||||
|
mImageReader.close(); |
||||||
|
mImageReader = null; |
||||||
|
} |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraClosed(); |
||||||
|
} |
||||||
|
} catch (InterruptedException e) { |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onCameraError(e); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
mCameraOpenCloseLock.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Starts a background thread and its {@link Handler}. |
||||||
|
*/ |
||||||
|
private void startBackgroundThread() { |
||||||
|
mBackgroundThread = new HandlerThread("CameraBackground"); |
||||||
|
mBackgroundThread.start(); |
||||||
|
mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Stops the background thread and its {@link Handler}. |
||||||
|
*/ |
||||||
|
private void stopBackgroundThread() { |
||||||
|
mBackgroundThread.quitSafely(); |
||||||
|
try { |
||||||
|
mBackgroundThread.join(); |
||||||
|
mBackgroundThread = null; |
||||||
|
mBackgroundHandler = null; |
||||||
|
} catch (InterruptedException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link CameraCaptureSession} for camera preview. |
||||||
|
*/ |
||||||
|
private void createCameraPreviewSession() { |
||||||
|
try { |
||||||
|
SurfaceTexture texture = mTextureView.getSurfaceTexture(); |
||||||
|
assert texture != null; |
||||||
|
|
||||||
|
// We configure the size of default buffer to be the size of camera preview we want.
|
||||||
|
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); |
||||||
|
|
||||||
|
// This is the output Surface we need to start preview.
|
||||||
|
Surface surface = new Surface(texture); |
||||||
|
|
||||||
|
// We set up a CaptureRequest.Builder with the output Surface.
|
||||||
|
mPreviewRequestBuilder |
||||||
|
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); |
||||||
|
|
||||||
|
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, |
||||||
|
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); |
||||||
|
|
||||||
|
mPreviewRequestBuilder.addTarget(surface); |
||||||
|
mPreviewRequestBuilder.addTarget(mImageReader.getSurface()); |
||||||
|
|
||||||
|
// Here, we create a CameraCaptureSession for camera preview.
|
||||||
|
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), |
||||||
|
mCaptureStateCallback, mBackgroundHandler |
||||||
|
); |
||||||
|
} catch (CameraAccessException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configures the necessary {@link Matrix} transformation to `mTextureView`. |
||||||
|
* This method should be called after the camera preview size is determined in |
||||||
|
* setUpCameraOutputs and also the size of `mTextureView` is fixed. |
||||||
|
* |
||||||
|
* @param viewWidth The width of `mTextureView` |
||||||
|
* @param viewHeight The height of `mTextureView` |
||||||
|
*/ |
||||||
|
private void configureTransform(int viewWidth, int viewHeight) { |
||||||
|
if (null == mTextureView || null == mPreviewSize) { |
||||||
|
return; |
||||||
|
} |
||||||
|
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)) % 360, centerX, centerY); |
||||||
|
} else if (Surface.ROTATION_180 == rotation) { |
||||||
|
matrix.postRotate(180, centerX, centerY); |
||||||
|
} |
||||||
|
Log.i(TAG, "configureTransform: " + getCameraOri(rotation, mCameraId) + " " + rotation * 90); |
||||||
|
mTextureView.setTransform(matrix); |
||||||
|
} |
||||||
|
|
||||||
|
public static final class Builder { |
||||||
|
|
||||||
|
private TextureView previewDisplayView; |
||||||
|
|
||||||
|
private String specificCameraId; |
||||||
|
|
||||||
|
private Camera2Listener camera2Listener; |
||||||
|
|
||||||
|
private Point previewViewSize; |
||||||
|
|
||||||
|
private int rotation; |
||||||
|
|
||||||
|
private Point previewSize; |
||||||
|
|
||||||
|
private Point maxPreviewSize; |
||||||
|
|
||||||
|
private Point minPreviewSize; |
||||||
|
|
||||||
|
private Context context; |
||||||
|
|
||||||
|
public Builder() { |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Builder previewOn(TextureView val) { |
||||||
|
previewDisplayView = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder previewSize(Point val) { |
||||||
|
previewSize = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder maxPreviewSize(Point val) { |
||||||
|
maxPreviewSize = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder minPreviewSize(Point val) { |
||||||
|
minPreviewSize = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder previewViewSize(Point val) { |
||||||
|
previewViewSize = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder rotation(int val) { |
||||||
|
rotation = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Builder specificCameraId(String val) { |
||||||
|
specificCameraId = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder cameraListener(Camera2Listener val) { |
||||||
|
camera2Listener = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Builder context(Context val) { |
||||||
|
context = val; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Camera2Helper build() { |
||||||
|
if (previewDisplayView == null) { |
||||||
|
throw new NullPointerException("must preview on a textureView or a surfaceView"); |
||||||
|
} |
||||||
|
if (maxPreviewSize != null && minPreviewSize != null) { |
||||||
|
if (maxPreviewSize.x < minPreviewSize.x || maxPreviewSize.y < minPreviewSize.y) { |
||||||
|
throw new IllegalArgumentException("maxPreviewSize must greater than minPreviewSize"); |
||||||
|
} |
||||||
|
} |
||||||
|
return new Camera2Helper(this); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener { |
||||||
|
private byte[] yPlane; |
||||||
|
private byte[] uPlane; |
||||||
|
private byte[] vPlane; |
||||||
|
private ReentrantLock lock = new ReentrantLock(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onImageAvailable(ImageReader reader) { |
||||||
|
Image image = reader.acquireNextImage(); |
||||||
|
// YUV_420_888
|
||||||
|
if (camera2Listener != null && image.getFormat() == ImageFormat.YUV_420_888) { |
||||||
|
Image.Plane[] planes = image.getPlanes(); |
||||||
|
lock.lock(); |
||||||
|
if (yPlane == null || uPlane == null || vPlane == null) { |
||||||
|
yPlane = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()]; |
||||||
|
uPlane = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()]; |
||||||
|
vPlane = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()]; |
||||||
|
} |
||||||
|
if (image.getPlanes()[0].getBuffer().remaining() == yPlane.length) { |
||||||
|
planes[0].getBuffer().get(yPlane); |
||||||
|
planes[1].getBuffer().get(uPlane); |
||||||
|
planes[2].getBuffer().get(vPlane); |
||||||
|
if (camera2Listener != null) { |
||||||
|
camera2Listener.onPreviewFrame(yPlane, uPlane, vPlane); |
||||||
|
} |
||||||
|
} |
||||||
|
lock.unlock(); |
||||||
|
} |
||||||
|
image.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
package com.frank.live.camera2; |
||||||
|
|
||||||
|
|
||||||
|
import android.util.Size; |
||||||
|
|
||||||
|
public interface Camera2Listener { |
||||||
|
|
||||||
|
void onCameraOpened(Size previewSize, int displayOrientation); |
||||||
|
|
||||||
|
void onPreviewFrame(byte[] y, byte[] u, byte[] v); |
||||||
|
|
||||||
|
void onCameraClosed(); |
||||||
|
|
||||||
|
void onCameraError(Exception e); |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue