//这里主要看停止的流程即stop = true时会进入onStopVideoRecording方法publicvoidonShutterButtonClick(){Log.d(TAG,"onShutterButtonClick");if(mSwitchingCamera ||!mAppController.getCameraAppUI().isModeCoverHide()){Log.d(TAG," mSwitchingCamera or isModeCoverHide not record!!!!!");return;}boolean stop = mMediaRecorderRecording;if(stop){long delta =SystemClock.uptimeMillis()- mRecordingStartTime;Log.i(TAG,"mRecordingStartTime = "+ mRecordingStartTime +", delta = "+ delta);if(delta <1500){Log.i(TAG,"recorder duration too short");return;}// CameraAppUI mishandles mode option enable/disable// for video, override that
mAppController.getCameraAppUI().enableModeOptions();onStopVideoRecording();}else{int countDownDuration = mActivity.getSettingsManager().getInteger(SettingsManager.SCOPE_GLOBAL,Keys.KEY_COUNTDOWN_DURATION);
mTimerDuration = countDownDuration;if(countDownDuration >0){// Start count down.
mAppController.getCameraAppUI().transitionToCancel();
mAppController.getCameraAppUI().hideModeOptions();
mUI.startCountdown(countDownDuration);return;}else{// CameraAppUI mishandles mode option enable/disable// for video, override that
mAppController.getCameraAppUI().disableModeOptions();startVideoRecording();}}
mAppController.setShutterEnabled(false);if(mCameraSettings !=null){
mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());}// Keep the shutter button disabled when in video capture intent// mode and recording is stopped. It'll be re-enabled when// re-take button is clicked.if(!(mIsVideoCaptureIntent && stop)){
mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);}}
onStopVideoRecording()
//这里可以看到有调用stopVideoRecording方法privatevoidonStopVideoRecording(){AudioManager am =(AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
am.abandonAudioFocus(null);
mAppController.getCameraAppUI().setSwipeEnabled(true);boolean recordFail =stopVideoRecording();if(mIsVideoCaptureIntent){if(mQuickCapture){doReturnToCaller(!recordFail);}elseif(!recordFail){showCaptureResult();}
mAppController.getCameraAppUI().disableModeOptions();}elseif(!recordFail){// Start capture animation.if(!mPaused &&ApiHelper.HAS_SURFACE_TEXTURE_RECORDING){// The capture animation is disabled on ICS because we use SurfaceView// for preview during recording. When the recording is done, we switch// back to use SurfaceTexture for preview and we need to stop then start// the preview. This will cause the preview flicker since the preview// will not be continuous for a short period of time.//mAppController.startFlashAnimation(false);}}}
stopVideoRecording()
privatebooleanstopVideoRecording(){// Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes// (b/17313985) without this check. Crash could also be reproduced by continuously tapping// on shutter button and preview with two fingers.if(mSnapshotInProgress){Log.v(TAG,"Skip stopVideoRecording since snapshot in progress");returntrue;}Log.v(TAG,"stopVideoRecording");// Re-enable sound as early as possible to avoid interfering with stop// recording sound.restoreRingerMode();
mUI.setSwipingEnabled(true);
mUI.showPassiveFocusIndicator();
mAppController.getCameraAppUI().setShouldSuppressCaptureIndicator(false);boolean fail =false;//因为mMediaRecorderRecording等于true,所以会走入下面的判断if(mMediaRecorderRecording){boolean shouldAddToMediaStoreNow =false;try{
mMediaRecorder.setOnErrorListener(null);
mMediaRecorder.setOnInfoListener(null);
mMediaRecorder.stop();//这里将shouldAddToMediaStoreNow设置为true
shouldAddToMediaStoreNow =true;
mCurrentVideoFilename = mVideoFilename;Log.v(TAG,"stopVideoRecording: current video filename: "+ mCurrentVideoFilename);}catch(RuntimeException e){Log.e(TAG,"stop fail", e);if(mVideoFilename !=null){deleteVideoFile(mVideoFilename);}
fail =true;}
mMediaRecorderRecording =false;
mActivity.unlockOrientation();// 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){// b/16300704: Monkey is fast so it could pause the module while recording.// stopPreview should definitely be called before switching off.stopPreview();closeCamera();}
mUI.showRecordingUI(false);// The orientation was fixed during video recording. Now make it// reflect the device orientation as video recording is stopped.
mUI.setOrientationIndicator(0,true);
mActivity.enableKeepScreenOn(false);//上面有将shouldAddToMediaStoreNow设置为true 同事fail默认为false,因此会走入下面的if语句if(shouldAddToMediaStoreNow &&!fail){//然后这里进入saveVideo方法if(mVideoFileDescriptor ==null){saveVideo();}elseif(mIsVideoCaptureIntent){// if no file save is needed, we can show the post capture UI nowshowCaptureResult();}}}// release media recorderreleaseMediaRecorder();
mAppController.getCameraAppUI().showModeOptions();
mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);if(!mPaused && mCameraDevice !=null){setFocusParameters();
mCameraDevice.lock();if(!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING){stopPreview();// Switch back to use SurfaceTexture for preview.startPreview();}// Update the parameters here because the parameters might have been altered// by MediaRecorder.
mCameraSettings = mCameraDevice.getSettings();}// Check this in advance of each shot so we don't add to shutter// latency. It's true that someone else could write to the SD card// in the mean time and fill it, but that could have happened// between the shutter press and saving the file too.
mActivity.updateStorageSpaceAndHint(null);return fail;}
@OverridepublicvoidaddVideo(String path,ContentValues values,OnMediaSavedListener l){// We don't set a queue limit for video saving because the file// is already in the storage. Only updating the database.newVideoSaveTask(path, values, l, mContentResolver).execute();}//VideoSaveTask在onPostExecute方法将uri通过构造函数中的OnMediaSavedListener即mOnVideoSavedListener将其传递出去privatefinalMediaSaver.OnMediaSavedListener mOnVideoSavedListener =newMediaSaver.OnMediaSavedListener(){@OverridepublicvoidonMediaSaved(Uri uri){if(uri !=null){
mCurrentVideoUri = uri;
mCurrentVideoUriFromMediaSaved =true;onVideoSaved();//然后通过notifyNewMedia又走到CameraActivity中的notifyNewMedia方法,从上面的log打印也可以看到
mActivity.notifyNewMedia(uri);}}};privateclassVideoSaveTaskextendsAsyncTask<Void,Void,Uri>{privateString path;privatefinalContentValues values;privatefinalOnMediaSavedListener listener;privatefinalContentResolver resolver;publicVideoSaveTask(String path,ContentValues values,OnMediaSavedListener l,ContentResolver r){this.path = path;this.values =newContentValues(values);this.listener = l;this.resolver = r;}@OverrideprotectedUridoInBackground(Void... v){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);
finalName = finalName.substring(0, finalName.length()-4);if(path.startsWith("/storage")&&!path.startsWith(Storage.FLASH_DIR)&&!(newFile(Storage.EXTENAL_SD).canWrite())){
path = path.replaceFirst("/storage/","/mnt/media_rw/");if(finalName.startsWith("/storage")&&!finalName.startsWith(Storage.FLASH_DIR))
finalName = finalName.replaceFirst("/storage/","/mnt/media_rw/");}File finalFile =newFile(finalName);if(newFile(path).renameTo(finalFile)){
path = finalName;}
values.put(Video.Media.DATA, 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;}@OverrideprotectedvoidonPostExecute(Uri uri){if(listener !=null){
listener.onMediaSaved(uri);}}}
@OverridepublicvoidnotifyNewMedia(Uri uri){if(mPaused){return;}// TODO: This method is running on the main thread. Also we should get// rid of that AsyncTask.updateStorageSpaceAndHint(null);ContentResolver cr =getContentResolver();String mimeType = cr.getType(uri);Log.v(TAG,"===============notifyNewMedia===================");if(mimeType ==null){Log.e(TAG,"Can't find video data in content resolver:"+ uri);return;}FilmstripItem newData =null;if(FilmstripItemUtils.isMimeTypeVideo(mimeType)){sendBroadcast(newIntent(CameraUtil.ACTION_NEW_VIDEO, uri).setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
newData = mVideoItemFactory.queryContentUri(uri);if(newData ==null){Log.e(TAG,"Can't find video data in content resolver:"+ uri);return;}}elseif(FilmstripItemUtils.isMimeTypeImage(mimeType)){CameraUtil.broadcastNewPicture(mAppContext, uri);
newData = mPhotoItemFactory.queryContentUri(uri);if(newData ==null){Log.e(TAG,"Can't find photo data in content resolver:"+ uri);return;}}else{Log.w(TAG,"Unknown new media with MIME type:"+ mimeType +", uri:"+ uri);return;}// We are preloading the metadata for new video since we need the// rotation info for the thumbnail.newAsyncTask<FilmstripItem,Void,FilmstripItem>(){@OverrideprotectedFilmstripItemdoInBackground(FilmstripItem... params){FilmstripItem data = params[0];MetadataLoader.loadMetadata(getAndroidContext(), data);return data;}@OverrideprotectedvoidonPostExecute(finalFilmstripItem data){// TODO: Figure out why sometimes the data is aleady there.
mDataAdapter.addOrUpdate(data);// Legacy modules don't use CaptureSession, so we show the capture indicator when// the item was safed.//不管是拍照还是录像模块都会进入if(mCurrentModule instanceofPhotoModule||
mCurrentModule instanceofVideoModule){AsyncTask.THREAD_POOL_EXECUTOR.execute(newRunnable(){@Overridepublicvoidrun(){Log.d(TAG,"generateThumbnail onPostExecute");//然后在这里获取缩略图,这里主要是通过FilmstripItem的实现类VideoItemfinalOptional<Bitmap> bitmap = data.generateThumbnail(
mAboveFilmstripControlLayout.getWidth(),
mAboveFilmstripControlLayout.getMeasuredHeight());//有缩略图就显示出来,但是log显示了一个空指针,因此可以判断肯定是获取缩略图有问题,导致无法显示if(bitmap.isPresent()){indicateCapture(bitmap.get(),0);}Log.d(TAG,"generateThumbnail onPostExecute end");}});}}}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);}VideoItem中generateThumbnail方法
@OverridepublicOptional<Bitmap>generateThumbnail(int boundingWidthPx,int boundingHeightPx){returnOptional.fromNullable(FilmstripItemUtils.loadVideoThumbnail(getData().getFilePath()));}
static sp<MediaMetadataRetrieverBase>createRetriever(player_type playerType){
sp<MediaMetadataRetrieverBase> p;char value[PROPERTY_VALUE_MAX];switch(playerType){case STAGEFRIGHT_PLAYER:case NU_PLAYER:{
p =new StagefrightMetadataRetriever;break;}//原生的Android10是没有这个case的,这里是RK添加的一个case,我们可以通过设置属性cts_gts.status 为true来进行验证//最后发现就是这里有问题,我们可以修改为使用原生的StagefrightMetadataRetriever即修复这个bugcase ROCKIT_PLAYER:{if(property_get("cts_gts.status", value,NULL)&&!strcasecmp("true", value)){
p =new StagefrightMetadataRetriever;}else{
p =new RockitMetadataRetriever;}break;}default:// TODO:// support for TEST_PLAYERALOGE("player type %d is not supported", playerType);break;}if(p ==NULL){ALOGE("failed to create a retriever object");}return p;}