乐趣区

关于android:Android入门教程-Camera-相机一

概述

Android 应用 Android Camera API 实现音视频的采集、编码、封包成 mp4 输入。

基于android.hardware.Camera,创立一个横屏利用,实时预览摄像头图像,实现录像并输入 MP4 的性能。这里不应用 Camera2。

申请权限

<!-- 须要录制音视频权限和写内部存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" />

在 activity 中动静申请权限

private static final String[] VIDEO_PERMISSIONS = {
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

实现摄像头预览性能

应用 SurfaceView 来预览。新建 CameraPreview 类继承自 SurfaceView 并实现SurfaceHolder.Callback;camera 相干操作都放在这个 View 里。

  • surfaceCreated中获取 Camera 实例,启动预览;设置预览相干参数
  • surfaceDestroyed开释 Camera
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = "rustAppCameraPreview";
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    private static int mOptVideoWidth = 1920;  // 默认视频帧宽度
    private static int mOptVideoHeight = 1080;
    private Uri outputMediaFileUri;
    private String outputMediaFileType;


    public CameraPreview(Context context) {super(context);
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    private static Camera getCameraInstance() {
        Camera c = null;
        try {c = Camera.open();
        } catch (Exception e) {Log.d(TAG, "camera is not available");
        }
        return c;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {mCamera = getCameraInstance();
        try {mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
            getCameraOptimalVideoSize(); // 找到最合适的分辨率} catch (IOException e) {Log.d(TAG, "Error setting camera preview:" + e.getMessage());
        }
    }

    private void getCameraOptimalVideoSize() {
        try {Camera.Parameters parameters = mCamera.getParameters();
            List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
            List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
            Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
                    mSupportedPreviewSizes, getWidth(), getHeight());
            mOptVideoWidth = optimalSize.width;
            mOptVideoHeight = optimalSize.height;
            Log.d(TAG, "prepareVideoRecorder: optimalSize:" + mOptVideoWidth + "," + mOptVideoHeight);
        } catch (Exception e) {Log.e(TAG, "getCameraOptimalVideoSize:", e);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {mHolder.removeCallback(this);
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {}}

在 Fragment 中显示摄像头预览

预置一个 FrameLayout,实例化一个 CameraPreview 增加进去

/**
 * 视频录制界面
 * Created by Rust on 2018/5/17.
 */
public class VideoRecordFragment extends Fragment {
    private static final String TAG = "rustAppVideoFrag";

    private Button mCaptureBtn;
    private CameraPreview mCameraPreview;

    public static VideoRecordFragment newInstance() {return new VideoRecordFragment();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        Log.d(TAG, "frag onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {Log.d(TAG, "frag onCreateView");
        return inflater.inflate(R.layout.frag_video_record, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {Log.d(TAG, "frag onViewCreated");
        super.onViewCreated(view, savedInstanceState);
        mCaptureBtn = view.findViewById(R.id.capture_btn);
        //mCaptureBtn.setOnClickListener(mOnClickListener);// 录制键

        mCameraPreview = new CameraPreview(getContext());
        FrameLayout preview = view.findViewById(R.id.camera_preview);
        preview.addView(mCameraPreview);
    }
}

应用 MediaRecorder 录制

MediaRecorder 指定参数后,调用 start() 开始录制,stop() 完结录制

录制开始前,获取 camera,mCamera.unlock() 解锁;录制结束后,革除 MediaRecordermCamera.lock()

private MediaRecorder mMediaRecorder;


    public boolean startRecording() {if (prepareVideoRecorder()) {mMediaRecorder.start();
            return true;
        } else {releaseMediaRecorder();
        }
        return false;
    }

    public void stopRecording() {if (mMediaRecorder != null) {mMediaRecorder.stop();
        }
        releaseMediaRecorder();}

    public boolean isRecording() {return mMediaRecorder != null;}

    private boolean prepareVideoRecorder() {if (null == mCamera) {mCamera = getCameraInstance();
        }
        mMediaRecorder = new MediaRecorder();

        mCamera.unlock();
        mMediaRecorder.setCamera(mCamera);

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

        mMediaRecorder.setVideoSize(mOptVideoWidth, mOptVideoHeight);
        mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
        mMediaRecorder.setPreviewDisplay(mHolder.getSurface());

        try {mMediaRecorder.prepare();
        } catch (IllegalStateException e) {Log.d(TAG, "IllegalStateException preparing MediaRecorder:" + e.getMessage());
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {Log.d(TAG, "IOException preparing MediaRecorder:" + e.getMessage());
            releaseMediaRecorder();
            return false;
        }
        return true;
    }

    private void releaseMediaRecorder() {if (mMediaRecorder != null) {mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;
            mCamera.lock();}
    }

    private File getOutputMediaFile(int type) {
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), TAG);
        if (!mediaStorageDir.exists()) {if (!mediaStorageDir.mkdirs()) {Log.d(TAG, "failed to create directory");
                return null;
            }
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                    "IMG_" + timeStamp + ".jpg");
            outputMediaFileType = "image/*";
        } else if (type == MEDIA_TYPE_VIDEO) {mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                    "VID_" + timeStamp + ".mp4");
            outputMediaFileType = "video/*";
        } else {return null;}
        outputMediaFileUri = Uri.fromFile(mediaFile);
        return mediaFile;
    }

后盾返回时预览黑屏的问题

CameraPreview 是咱们在 Fragment 创立时实例化并增加进去的。利用退到后盾后,CameraPreview 曾经被销毁。利用回到前台时,咱们应该在 onResume 办法中进行操作。复原CameraPreview

在 Fragment 中,判断销毁和重建预览的机会。

@Override
    public void onPause() {super.onPause();
        Log.d(TAG, "onPause: 销毁预览");
        mCameraPreview = null;
    }

    @Override
    public void onResume() {super.onResume();
        Log.d(TAG, "onResume: 回到前台");
        if (null == mCameraPreview) {initCameraPreview();
        }
    }

    private void initCameraPreview() {mCameraPreview = new CameraPreview(getContext());
        FrameLayout preview = mRoot.findViewById(R.id.camera_preview);
        preview.addView(mCameraPreview);
    }

【Android 音视频开发系列教程】

退出移动版