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 里面供其他应用使用。
博文到这就告一段落了
谢谢大家,祝你生活愉快。