zoukankan      html  css  js  c++  java
  • 音视频的采集、编码、封包成 mp4 输出

    使用 Android Camera API 完成音视频的采集、编码、封包成 mp4 输出
    基于android.hardware.Camera,创建一个横屏应用,实时预览摄像头图像,实现录像并输出MP4的功能。

    1、申请权限

    <!-- 需要录制音视频权限和写外部存储权限 -->
    <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
    };
    

    2、实现摄像头预览功能

    使用SurfaceView来预览。新建CameraPreview类继承自SurfaceView并实现SurfaceHolder.Callback;Camera相关操作都放在这个View里。surfaceCreated中获取Camera实例,启动预览;设置预览相关参数,surfaceDestroyed释放Camera

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        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) {
        }
    }
    

    3、在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()解锁;录制完毕后,清除MediaRecorder,mCamera.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);
    }
    

    原创作者:CHSmile,原文链接:https://www.jianshu.com/p/b6386ba20d08

  • 相关阅读:
    (转)jqgrid 显示格式化数据
    (转)JS 中如何将字符串转化成日期&&日期格式化方法
    (转)PowerDesigner 12.5下载
    SQL DATEADD语法
    Javascript 版本utf8转换为gb2312
    解决“Lc.exe 已退出,代码 1”错误
    Oracle多表关联更新
    ORACLE合并查询结果
    DataGrid 格式化字段
    动态类帮助器
  • 原文地址:https://www.cnblogs.com/hejunlin/p/12629155.html
Copyright © 2011-2022 走看看