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 里面供其他应用使用。
博文到这就告一段落了
谢谢大家,祝你生活愉快。
更多相关文章
- 第17天android:《android从零开始》视频(1-5)
- Android(安卓)关于获取摄像头帧数据
- 【Android】如何用MediaPlayer实现一个简单的音视频播放器
- android音频、视频、拍照基础操作
- Android(安卓)拍照及相册选图的那些坑
- android 照相源码
- android视频截图
- android获取本地视频路径
- android视频录制(调用系统视频录制),生成缩略图