Android9.0 Camera App代码跟踪之Camrea视频拍摄

Android9.0 Camera App的视频拍摄用到了/android/frameworks/base/media/java/android/media/MediaRecorder.java类

的api

/**
 * Used to record audio and video. The recording control is based on a
 * simple state machine (see below).
 *
 * 

*

* *

A common case of using MediaRecorder to record audio works as follows: * *

MediaRecorder recorder = new MediaRecorder();
 * recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 * recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
 * recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 * recorder.setOutputFile(PATH_NAME);
 * recorder.prepare();
 * recorder.start();   // Recording is now started
 * ...
 * recorder.stop();
 * recorder.reset();   // You can reuse the object by going back to setAudioSource() step
 * recorder.release(); // Now the object cannot be reused
 * 
* *

Applications may want to register for informational and error * events in order to be informed of some internal update and possible * runtime errors during recording. Registration for such events is * done by setting the appropriate listeners (via calls * (to {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener and/or * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener). * In order to receive the respective callback associated with these listeners, * applications are required to create MediaRecorder objects on threads with a * Looper running (the main UI thread by default already has a Looper running). * *

Note: Currently, MediaRecorder does not work on the emulator. * *

*

Developer Guides

*

For more information about how to use MediaRecorder for recording video, read the * Camera developer guide. * For more information about how to use MediaRecorder for recording sound, read the * Audio Capture developer guide.

*
*/ public class MediaRecorder implements AudioRouting { ..... }

怎么用注释写的清清楚楚

好,接下来从VideoModule的init()方法开始讲起

@Override
    public void init(CameraActivity activity, View root) {
        mActivity = activity;
        mUI = new VideoUI(activity, this, root);
        mPreferences = ComboPreferences.get(mActivity);
        if (mPreferences == null) {
            mPreferences = new ComboPreferences(mActivity);
        }

        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal(), activity);
        mCameraId = getPreferredCameraId(mPreferences);

        mPreferences.setLocalId(mActivity, mCameraId);
        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());

        mOrientationManager = new OrientationManager(mActivity);

        /*
         * To reduce startup time, we start the preview in another thread.
         * We make sure the preview is started at the end of onCreate.
         */
        CameraOpenThread cameraOpenThread = new CameraOpenThread();
        cameraOpenThread.start();//开启线程CameraOpenThread打开相机,这里的相机是取CameraHolder里面的Camera对象,这样就不用重复去实例化对象了

        mContentResolver = mActivity.getContentResolver();
//SD卡工具类
        Storage.setSaveSDCard(
            mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0").equals("1"));
        mSaveToSDCard = Storage.isSaveSDCard();
        // Surface texture is from camera screen nail and startPreview needs it.
        // This must be done before startPreview.
        mIsVideoCaptureIntent = isVideoCaptureIntent();
        initializeSurfaceView();//初始化SurfaceView

        // Make sure camera device is opened.
        try {
            cameraOpenThread.join();
            if (mCameraDevice == null) {
                return;
            }
        } catch (InterruptedException ex) {
            // ignore
        }
//读取视频配置参数
        readVideoPreferences();
        mUI.setPrefChangedListener(this);

        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
        mLocationManager = new LocationManager(mActivity, this);

        mUI.setOrientationIndicator(0, false);
        setDisplayOrientation();//视频显示方向

        mUI.showTimeLapseUI(mCaptureTimeLapse);
        initializeVideoSnapshot();
        resizeForPreviewAspectRatio();

        initializeVideoControl();//视频控制
        mPendingSwitchCameraId = -1;
    }

接这看下预览方法

private void startPreview() {
        Log.v(TAG, "startPreview");
        mStartPrevPending = true;

        SurfaceHolder sh = null;
        Log.v(TAG, "startPreview: SurfaceHolder (MDP path)");
        sh = mUI.getSurfaceHolder();

        if (!mPreferenceRead || mPaused == true || mCameraDevice == null) {
            mStartPrevPending = false;
            return;
        }
        mErrorCallback.setActivity(mActivity);
        mCameraDevice.setErrorCallback(mErrorCallback);
        if (mPreviewing == true) {
            stopPreview();
        }

        setDisplayOrientation();
        mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
        setCameraParameters(true);

        try {
//关键代码在这里,绑定SurfaceView,熟悉的配方熟悉的套路
            mCameraDevice.setPreviewDisplay(sh);
            mCameraDevice.setOneShotPreviewCallback(mHandler,
                new CameraManager.CameraPreviewDataCallback() {
                    @Override
                    public void onPreviewFrame(byte[] data, CameraProxy camera) {
                        mUI.hidePreviewCover();
                    }
                });
            mCameraDevice.startPreview();//开始预览
            mPreviewing = true;
            onPreviewStarted();
        } catch (Throwable ex) {
            closeCamera();
            throw new RuntimeException("startPreview failed", ex);
        }
        mStartPrevPending = false;
    }

这里要讲一下的是当视频的配置参数有变时是需要重新加载预览的

 @Override
    public void onSharedPreferenceChanged() {
        // ignore the events after "onPause()" or preview has not started yet
        if (mPaused) {
            return;
        }
        synchronized (mPreferences) {
            // If mCameraDevice is not ready then we can set the parameter in
            // startPreview().
            if (mCameraDevice == null) return;

            boolean recordLocation = RecordLocationPreference.get(mPreferences,
                    CameraSettings.KEY_RECORD_LOCATION);
            mLocationManager.recordLocation(recordLocation);

            readVideoPreferences();
            mUI.showTimeLapseUI(mCaptureTimeLapse);
            // We need to restart the preview if preview size is changed.
            Size size = mParameters.getPreviewSize();
            if (size.width != mDesiredPreviewWidth
                    || size.height != mDesiredPreviewHeight || mRestartPreview) {

                stopPreview();
                resizeForPreviewAspectRatio();
                startPreview(); // Parameters will be set in startPreview().
            } else {
                setCameraParameters(false);
            }
            mRestartPreview = false;
            mUI.updateOnScreenIndicators(mParameters, mPreferences);
            Storage.setSaveSDCard(
                mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0").equals("1"));
            mActivity.updateStorageSpaceAndHint();
        }
    }

我们接着讲视频拍摄按钮按下后的代码流程

private boolean startVideoRecording() {
        ...

        initializeRecorder();//初始化MediaRecorder
        ...
        requestAudioFocus();//关闭其他应用的音乐等多媒体

        try {
            mMediaRecorder.start(); // 开始拍摄
        } catch (RuntimeException e) {
           ...
        }

        // Make sure the video recording has started before announcing
        // this in accessibility.
        AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
                mActivity.getString(R.string.video_recording_started));

        // The parameters might have been altered by MediaRecorder already.
        // We need to force mCameraDevice to refresh before getting it.
        mCameraDevice.refreshParameters();//刷新参数
        // The parameters may have been changed by MediaRecorder upon starting
        // recording. We need to alter the parameters if we support camcorder
        // zoom. To reduce latency when setting the parameters during zoom, we
        // update mParameters here once.
        mParameters = mCameraDevice.getParameters();

           ......
        return true;
    }

// Prepares media recorder.
    private void initializeRecorder() {
        ....
        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
            // Set the SurfaceView to visible so the surface gets created.
            // surfaceCreated() is called immediately when the visibility is
            // changed to visible. Thus, mSurfaceViewReady should become true
            // right after calling setVisibility().
            mUI.showSurfaceView();//展示视频拍摄的实时画面
        }

        ....
        mMediaRecorder = new MediaRecorder();//实例化MediaRecorder

        // Unlock the camera object before passing it to media recorder.
        mCameraDevice.unlock();
        mMediaRecorder.setCamera(mCameraDevice.getCamera());//绑定一个Camera实例用于拍摄

        ....
        //到这里已经很熟悉了,就是开篇讲的MediaRecorder的用法
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mProfile.videoCodec = mVideoEncoder;
        mProfile.audioCodec = mAudioEncoder;
        mProfile.duration = mMaxVideoDurationInMs;

        if ((mProfile.audioCodec == MediaRecorder.AudioEncoder.AMR_NB) &&
            !mCaptureTimeLapse && !isHFR) {
            mProfile.fileFormat = MediaRecorder.OutputFormat.THREE_GPP;
        }
        // Set params individually for HFR case, as we do not want to encode audio
        if ((isHFR || isHSR) && captureRate > 0) {
            if (isHSR) {
                Log.i(TAG, "Enabling audio for HSR");
                mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
            }
            mMediaRecorder.setOutputFormat(mProfile.fileFormat);
            mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate);
            mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);
            mMediaRecorder.setVideoEncoder(mProfile.videoCodec);
            if (isHSR) {
                Log.i(TAG, "Configuring audio for HSR");
                mMediaRecorder.setAudioEncodingBitRate(mProfile.audioBitRate);
                mMediaRecorder.setAudioChannels(mProfile.audioChannels);
                mMediaRecorder.setAudioSamplingRate(mProfile.audioSampleRate);
                mMediaRecorder.setAudioEncoder(mProfile.audioCodec);
            }
        } else {
            if (!mCaptureTimeLapse) {
                mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
            }

            mMediaRecorder.setProfile(mProfile);
        }

        mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
        if (mCaptureTimeLapse) {
            double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
            setCaptureRate(mMediaRecorder, fps);
        } else if (captureRate > 0) {
            Log.i(TAG, "Setting capture-rate = " + captureRate);
            mMediaRecorder.setCaptureRate(captureRate);

            // for HSR, encoder's target-framerate = capture-rate
            // for HFR, encoder's taget-framerate = 30fps (from profile)
            int targetFrameRate = isHSR ? captureRate :
                    isHFR ? 30 : mProfile.videoFrameRate;

            Log.i(TAG, "Setting target fps = " + targetFrameRate);
            mMediaRecorder.setVideoFrameRate(targetFrameRate);

            // Profiles advertizes bitrate corresponding to published framerate.
            // In case framerate is different, scale the bitrate
            int scaledBitrate = getHighSpeedVideoEncoderBitRate(mProfile, targetFrameRate);
            Log.i(TAG, "Scaled Video bitrate : " + scaledBitrate);
            mMediaRecorder.setVideoEncodingBitRate(scaledBitrate);
        }

        setRecordLocation();

        // Set output file.
        // Try Uri in the intent first. If it doesn't exist, use our own
        // instead.
        if (mVideoFileDescriptor != null) {
            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
        } else {
            generateVideoFilename(mProfile.fileFormat);
            mMediaRecorder.setOutputFile(mVideoFilename);
        }

        // Set maximum file size.
        long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
            maxFileSize = requestedSizeLimit;
        }

        if (Storage.isSaveSDCard() && maxFileSize > SDCARD_SIZE_LIMIT) {
            maxFileSize = SDCARD_SIZE_LIMIT;
        }

        try {
            mMediaRecorder.setMaxFileSize(maxFileSize);
        } catch (RuntimeException exception) {
            // We are going to ignore failure of setMaxFileSize here, as
            // a) The composer selected may simply not support it, or
            // b) The underlying media framework may not handle 64-bit range
            // on the size restriction.
        }

        // See android.hardware.Camera.Parameters.setRotation for
        // documentation.
        // Note that mOrientation here is the device orientation, which is the opposite of
        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
        // which is the orientation the graphics need to rotate in order to render correctly.
        int rotation = 0;
        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
            CameraHolder.CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
            if (info.facing == CameraHolder.CameraInfo.CAMERA_FACING_FRONT) {
                rotation = (info.orientation - mOrientation + 360) % 360;
            } else {  // back-facing camera
                rotation = (info.orientation + mOrientation) % 360;
            }
        }
        mMediaRecorder.setOrientationHint(rotation);
        setupMediaRecorderPreviewDisplay();

        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
            releaseMediaRecorder();
            throw new RuntimeException(e);
        }

        mMediaRecorder.setOnErrorListener(this);
        mMediaRecorder.setOnInfoListener(this);
    }

当用户按下拍摄按钮后再此点击停止拍摄

private boolean stopVideoRecording() {
        Log.v(TAG, "stopVideoRecording");
        mStopRecPending = true;
        mUI.setSwipingEnabled(true);
        if (!isVideoCaptureIntent()) {
            mUI.showSwitcher();
        }

        boolean fail = false;
        if (mMediaRecorderRecording) {
            boolean shouldAddToMediaStoreNow = false;

            try {
                mMediaRecorder.setOnErrorListener(null);
                mMediaRecorder.setOnInfoListener(null);
                mMediaRecorder.stop();//停止记录
                shouldAddToMediaStoreNow = true;
                mCurrentVideoFilename = mVideoFilename;
                Log.v(TAG, "stopVideoRecording: Setting current video filename: "
                        + mCurrentVideoFilename);
                AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
                        mActivity.getString(R.string.video_recording_stopped));
            } catch (RuntimeException e) {
                Log.e(TAG, "stop fail",  e);
                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
                fail = true;
            }
            mMediaRecorderRecording = false;

            //If recording stops while snapshot is in progress, we might not get jpeg callback
            //because cameraservice will disable picture related messages. Hence reset the
            //flag here so that we can take liveshots in the next recording session.
            mSnapshotInProgress = false;
            showVideoSnapshotUI(false);

            // If the activity is paused, this means activity is interrupted
            // during recording. Release the camera as soon as possible because
            // face unlock or other applications may need to use the camera.
            if (mPaused) {
                closeCamera();
            }

            mUI.showRecordingUI(false);
            if (!mIsVideoCaptureIntent) {
                mUI.enableCameraControls(true);
            }
            // The orientation was fixed during video recording. Now make it
            // reflect the device orientation as video recording is stopped.
            mUI.setOrientationIndicator(0, true);
            keepScreenOnAwhile();
            if (shouldAddToMediaStoreNow && !fail) {
                if (mVideoFileDescriptor == null) {
                    saveVideo();
                } else if (mIsVideoCaptureIntent) {
                    // if no file save is needed, we can show the post capture UI now
                    showCaptureResult();//显示视频拍摄结果界面
                }
            }
        }
        // release media recorder
        releaseMediaRecorder();
        releaseAudioFocus();
        if (!mPaused) {
            mCameraDevice.lock();
            if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
                stopPreview();
                mUI.hideSurfaceView();
                // Switch back to use SurfaceTexture for preview.
                startPreview();//开启预览
            }
        }
        // Update the parameters here because the parameters might have been altered
        // by MediaRecorder.
        if (!mPaused) mParameters = mCameraDevice.getParameters();
        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
                fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
                        UsageStatistics.ACTION_CAPTURE_DONE, "Video",
                mMediaRecorderPausing ? mRecordingTotalTime :
                        SystemClock.uptimeMillis() - mRecordingStartTime + mRecordingTotalTime);
        mStopRecPending = false;
        return fail;
    }

那么视频文件是如何保存的呢?请看saveVideo() 方法

private void saveVideo() {
        if (mVideoFileDescriptor == null) {
            File origFile = new File(mCurrentVideoFilename);
            if (!origFile.exists() || origFile.length() <= 0) {
                Log.e(TAG, "Invalid file");
                mCurrentVideoValues = null;
                return;
            }

            long duration = 0L;
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();

            try {
                retriever.setDataSource(mCurrentVideoFilename);
                duration = Long.valueOf(retriever.extractMetadata(
                            MediaMetadataRetriever.METADATA_KEY_DURATION));
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "cannot access the file");
            }
            retriever.release();
//这里调用MediaSaveService服务来保存视频文件
            mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
                    duration, mCurrentVideoValues,
                    mOnVideoSavedListener, mContentResolver);
        }
        mCurrentVideoValues = null;
    }
//MediaSaveService类的方法
public void addVideo(String path, long duration, ContentValues values,
            OnMediaSavedListener l, ContentResolver resolver) {
        // We don't set a queue limit for video saving because the file
        // is already in the storage. Only updating the database.
        new VideoSaveTask(path, duration, values, l, resolver).execute();
    }
//开启异步任务保存视频文件
private class VideoSaveTask extends AsyncTask  {
        private String path;
        private long duration;
        private ContentValues values;
        private OnMediaSavedListener listener;
        private ContentResolver resolver;

        public VideoSaveTask(String path, long duration, ContentValues values,
                OnMediaSavedListener l, ContentResolver r) {
            this.path = path;
            this.duration = duration;
            this.values = new ContentValues(values);
            this.listener = l;
            this.resolver = r;
        }

        @Override
        protected Uri doInBackground(Void... v) {
            values.put(Video.Media.SIZE, new File(path).length());
            values.put(Video.Media.DURATION, duration);
            Uri uri = null;
            try {
                Uri videoTable = Uri.parse(VIDEO_BASE_URI);
                uri = resolver.insert(videoTable, values);

                // Rename the video file to the final name. This avoids other
                // apps reading incomplete data.  We need to do it after we are
                // certain that the previous insert to MediaProvider is completed.
                String finalName = values.getAsString(
                        Video.Media.DATA);
                if (new File(path).renameTo(new File(finalName))) {
                    path = finalName;
                }

                resolver.update(uri, values, null, null);
            } catch (Exception e) {
                // We failed to insert into the database. This can happen if
                // the SD card is unmounted.
                Log.e(TAG, "failed to add video to media store", e);
                uri = null;
            } finally {
                Log.v(TAG, "Current video URI: " + uri);
            }
            return uri;
        }

        @Override
        protected void onPostExecute(Uri uri) {
            if (listener != null) listener.onMediaSaved(uri);
        }
    }
}

这里的视频文件是放到了ContentProvider 里面供其他应用使用。

博文到这就告一段落了

谢谢大家,祝你生活愉快。

你可能感兴趣的:(Android系统开发,笔记)