一、Camera
1.1、结合SurfaceView实现预览
1.1.1、布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".CameraActivity">
<SurfaceView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
1.1.2、实现预览
mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {
@Override
public void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
- Camera.open()
打开摄像头 - setPreviewDisplay
设置预览展示的控件 - startPreview
开始预览
发现预览是横着的,需要使用setDisplayOrientation调整预览图像的方向
1.1.3、获取摄像头的原始数据
mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {
@Override
public void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewCallback(new PreviewCallBack());
mCamera.startPreview();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
private class PreviewCallBack implements Camera.PreviewCallback {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
try {
bos.write(data);
Camera.Size previewSize = camera.getParameters().getPreviewSize();
Log.e(TAG, "onPreviewFrame:" + previewSize.width + "x" + previewSize.height);
Log.e(TAG, "Image format:" + camera.getParameters().getPictureFormat());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- setPreviewCallback
设置预览数据的回调 - 2560*1440
默认返回图像的分辨率 - Image format:256
默认返回图像的数据格式是JPEG的
1.1.4、调整相机数据的分辨率和格式
Parameters
private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int desiredWidth, int desiredHeight) {
List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();
Camera.Size bestSize = null;
int bestDiff = Integer.MAX_VALUE;
for (Camera.Size size : supportedSizes) {
int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);
if (diff < bestDiff) {
bestSize = size;
bestDiff = diff;
}
}
return bestSize;
}
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
Camera.Parameters parameters = mCamera.getParameters();
// 设置预览尺寸
int desiredWidth = 1280; // 设置所需的宽度
int desiredHeight = 720; // 设置所需的高度
Camera.Size bestSize = getBestPreviewSize(parameters, desiredWidth, desiredHeight);
parameters.setPreviewSize(bestSize.width, bestSize.height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
//设置输出数据的格式为NV21
parameters.setPictureFormat(ImageFormat.NV21);
mCamera.setParameters(parameters);
mCamera.setPreviewCallback(new PreviewCallBack());
mCamera.setDisplayOrientation(90);
mCamera.startPreview();
} catch (IOException e) {
throw new RuntimeException(e);
}
ImageFormat里面还定义的有YUV_420_888(yuv420)、YV12,那我们可以使用吗?
运行后,应用直接抛出异常了,我用的真机是VIVO x20的,android版本是8.1的,所以,不支持设置输出的摄像头数据为YUV_420_888的。
1.1.5、保存摄像头输出的nv21格式的数据
package com.aniljing.androidcamera;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;
import com.aniljing.androidcamera.databinding.ActivityCameraBinding;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
public class CameraActivity extends AppCompatActivity {
private final String TAG = CameraActivity.class.getSimpleName();
private Context mContext;
private ActivityCameraBinding mBinding;
private Camera mCamera;
private File mFile = new File(Environment.getExternalStorageDirectory(), "nv21.yuv");
private BufferedOutputStream bos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
mBinding = ActivityCameraBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
try {
if (mFile.exists()) {
mFile.delete();
}
bos = new BufferedOutputStream(new FileOutputStream(mFile));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
mBinding.preview.getHolder().addCallback(new SurfaceHolder.Callback2() {
@Override
public void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
Camera.Parameters parameters = mCamera.getParameters();
// 设置预览尺寸
int desiredWidth = 1280; // 设置所需的宽度
int desiredHeight = 720; // 设置所需的高度
Camera.Size bestSize = getBestPreviewSize(parameters, desiredWidth, desiredHeight);
parameters.setPreviewSize(bestSize.width, bestSize.height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setPictureFormat(ImageFormat.NV21);
mCamera.setParameters(parameters);
mCamera.setPreviewCallback(new PreviewCallBack());
mCamera.setDisplayOrientation(90);
mCamera.startPreview();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
}
private class PreviewCallBack implements Camera.PreviewCallback {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
try {
bos.write(data);
Camera.Size previewSize = camera.getParameters().getPreviewSize();
Log.e(TAG, "onPreviewFrame:" + previewSize.width + "x" + previewSize.height);
Log.e(TAG,"Image format:"+camera.getParameters().getPictureFormat());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
try {
bos.flush();
bos.close();
bos = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Camera.Size getBestPreviewSize(Camera.Parameters parameters, int desiredWidth, int desiredHeight) {
List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();
Camera.Size bestSize = null;
int bestDiff = Integer.MAX_VALUE;
for (Camera.Size size : supportedSizes) {
int diff = Math.abs(size.width - desiredWidth) + Math.abs(size.height - desiredHeight);
if (diff < bestDiff) {
bestSize = size;
bestDiff = diff;
}
}
return bestSize;
}
}
-
保存为yuv格式的文件
-
使用YuvEye工具打开nv21.yuv文件
-
可以看出,虽然预览已经可以竖屏显示视频了,但是我们实际保存的数据却还是横屏的,所以就需要我们手动的调整
-
如果我们选择其他格式的,使用工具是否能够正常打开呢?
我们会发现,柜子的颜色和使用正确的格式有很明显的差别的 -
如果我们输入错误的分辨率,又是什么效果呢?
1.1.6、数据旋转
java方式:
private void nv21_rotate_to_90(byte[] nv21_data, byte[] nv21_rotated, int width, int height) {
int y_size = width * height;
int buffser_size = y_size * 3 / 2;
// Rotate the Y luma
int i = 0;
int startPos = (height - 1) * width;
for (int x = 0; x < width; x++) {
int offset = startPos;
for (int y = height - 1; y >= 0; y--) {
nv21_rotated[i] = nv21_data[offset + x];
i++;
offset -= width;
}
}
// Rotate the U and V color components
i = buffser_size - 1;
for (int x = width - 1; x > 0; x = x - 2) {
int offset = y_size;
for (int y = 0; y < height / 2; y++) {
nv21_rotated[i] = nv21_data[offset + x];
i--;
nv21_rotated[</