一. Camera2Helper
public class Camera2Helper {
private Camera2Listener camera2Listener;
private Context context;
private TextureView mTextureView;
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private Size mPreviewSize;
private Point previewViewSize;
private ImageReader mImageReader;
private CameraDevice mCameraDevice;
private CaptureRequest.Builder mPreviewRequestBuilder;
private CameraCaptureSession mCaptureSession;
public Camera2Helper(Context context) {
this.context = context;
this.camera2Listener = (Camera2Listener) context;
}
public synchronized void start(TextureView textureView) {
this.mTextureView = textureView;
startBackgroundThread();
openCamera();
}
public synchronized void openCamera() {
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
try {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics("0");
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
// 获取读取流数据
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(
new OnImageAvailableListenerImpl(), mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
try {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
// 打开相机
cameraManager.openCamera("0", mDeviceStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
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;
}
};
private void createCameraPreviewSession() {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);
try {
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());
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), mCaptureStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (null == mCameraDevice) {
return;
}
mCaptureSession = cameraCaptureSession;
try {
// 设置预览界面 开始预览
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
new CameraCaptureSession.CaptureCallback() {
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
};
private Size getBestSupportedSize(List<Size> sizes) {
Point maxPreviewSize = new Point(1920, 1080);
Point minPreviewSize = new Point(1280, 720);
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) {
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 (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {
bestSize = s;
}
}
return bestSize;
}
/**
* Starts a background thread and its {@link Handler}.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
private byte[] y;
private byte[] u;
private byte[] v;
private ReentrantLock lock = new ReentrantLock();
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
// Y:U:V == 4:2:2
if (camera2Listener != null && image.getFormat() == ImageFormat.YUV_420_888) {
Image.Plane[] planes = image.getPlanes();
// 加锁确保y、u、v来源于同一个Image
lock.lock();
if (y == null) {
y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
}
if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
camera2Listener.onPreview(y, u, v, mPreviewSize, planes[0].getRowStride());
}
lock.unlock();
}
image.close();
}
}
public interface Camera2Listener {
/**
* 预览数据回调
* @param y 预览数据,Y分量
* @param u 预览数据,U分量
* @param v 预览数据,V分量
* @param previewSize 预览尺寸
* @param stride 步长
*/
void onPreview(byte[] y, byte[] u, byte[] v, Size previewSize, int stride);
}
}
二. MainActivity
public class MainActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener, Camera2Helper.Camera2Listener {
private static final String TAG = "liuyi";
private TextureView textureView;
private Camera2Helper camera2Helper;
private byte[] nv21;
private byte[] nv21_rotated;
private byte[] nv12;
private MediaCodec mediaCodec;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = findViewById(R.id.texture_preview);
textureView.setSurfaceTextureListener(this);
}
private boolean checkPermissions() {
boolean allGranted = true;
allGranted &= ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
}
return allGranted;
}
private void initCamera() {
camera2Helper = new Camera2Helper(this);
camera2Helper.start(textureView);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
if (!checkPermissions()) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
initCamera();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
@Override
public void onPreview(byte[] y, byte[] u, byte[] v, Size previewSize, int stride) {
if (nv21 == null) {
nv21 = new byte[stride * previewSize.getHeight() * 3 / 2];
nv21_rotated = new byte[stride * previewSize.getHeight() * 3 / 2];
}
if (mediaCodec == null) {
initCodec(previewSize);
}
ImageUtil.yuvToNv21(y, u, v, nv21, stride, previewSize.getHeight());
ImageUtil.nv21_rotate_to_90(nv21, nv21_rotated, stride, previewSize.getHeight());
byte[] temp = ImageUtil.nv21toNV12(nv21_rotated, nv12);
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int inIndex = mediaCodec.dequeueInputBuffer(100000);
if (inIndex >= 0) {
ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inIndex);
byteBuffer.clear();
byteBuffer.put(temp, 0, temp.length);
mediaCodec.queueInputBuffer(inIndex, 0, temp.length, 0, 0);
}
int outIndex = mediaCodec.dequeueOutputBuffer(info, 100000);
if (outIndex >= 0) {
ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(outIndex);
byte[] ba = new byte[byteBuffer.remaining()];
byteBuffer.get(ba);
writeContent(ba);
writeBytes(ba);
mediaCodec.releaseOutputBuffer(outIndex, false);
}
}
public String writeContent(byte[] array) {
char[] HEX_CHAR_TABLE = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
StringBuilder sb = new StringBuilder();
for (byte b : array) {
sb.append(HEX_CHAR_TABLE[(b & 0xf0) >> 4]);
sb.append(HEX_CHAR_TABLE[b & 0x0f]);
}
Log.i(TAG, "writeContent: " + sb.toString());
FileWriter writer = null;
try {
// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
writer = new FileWriter(Environment.getExternalStorageDirectory() + "/codec.txt", true);
writer.write(sb.toString());
writer.write("\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
public void writeBytes(byte[] array) {
FileOutputStream writer = null;
try {
// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
writer = new FileOutputStream(Environment.getExternalStorageDirectory() + "/codec.h264", true);
writer.write(array);
writer.write('\n');
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void initCodec(Size size) {
try {
mediaCodec = MediaCodec.createEncoderByType("video/avc");
final MediaFormat format = MediaFormat.createVideoFormat("video/avc", size.getHeight(), size.getWidth());
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
format.setInteger(MediaFormat.KEY_BIT_RATE, 4000_000);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//2s一个I帧
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
三. 代码地址
https://gitee.com/luisliuyi/android-mediacodec-camera2.git
网友评论