
RK3399 Android 10相机录像保存时无法获取缩略预览图

public void onShutterButtonClick() {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;}mAppController.getCameraAppUI().enableModeOptions();onStopVideoRecording();} else {int countDownDuration = mActivity.getSettingsManager().getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);mTimerDuration = countDownDuration;if (countDownDuration > 0) {mAppController.getCameraAppUI().transitionToCancel();mAppController.getCameraAppUI().hideModeOptions();mUI.startCountdown(countDownDuration);return;} else {mAppController.getCameraAppUI().disableModeOptions();startVideoRecording();}}mAppController.setShutterEnabled(false);if (mCameraSettings != null) {mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());}if (!(mIsVideoCaptureIntent && stop)) {mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);}}
private void onStopVideoRecording() {AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);am.abandonAudioFocus(null);mAppController.getCameraAppUI().setSwipeEnabled(true);boolean recordFail = stopVideoRecording();if (mIsVideoCaptureIntent) {if (mQuickCapture) {doReturnToCaller(!recordFail);} else if (!recordFail) {showCaptureResult();}mAppController.getCameraAppUI().disableModeOptions();} else if (!recordFail){if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {}}}
private boolean stopVideoRecording() {if (mSnapshotInProgress) {Log.v(TAG, "Skip stopVideoRecording since snapshot in progress");return true;}Log.v(TAG, "stopVideoRecording");restoreRingerMode();mUI.setSwipingEnabled(true);mUI.showPassiveFocusIndicator();mAppController.getCameraAppUI().setShouldSuppressCaptureIndicator(false);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: 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 (mPaused) {stopPreview();closeCamera();}mUI.showRecordingUI(false);mUI.setOrientationIndicator(0, true);mActivity.enableKeepScreenOn(false);if (shouldAddToMediaStoreNow && !fail) {if (mVideoFileDescriptor == null) {saveVideo();} else if (mIsVideoCaptureIntent) {showCaptureResult();}}}releaseMediaRecorder();mAppController.getCameraAppUI().showModeOptions();mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);if (!mPaused && mCameraDevice != null) {setFocusParameters();mCameraDevice.lock();if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {stopPreview();startPreview();}mCameraSettings = mCameraDevice.getSettings();}mActivity.updateStorageSpaceAndHint(null);return fail;}
- saveVideo()里面是通过MediaSaver接口的实现类MediaSaverImpI.addVideo方法
private void saveVideo() {if (mVideoFileDescriptor == null) {long duration = SystemClock.uptimeMillis() - mRecordingStartTime;if (duration > 0) {} else {Log.w(TAG, "Video duration <= 0 : " + duration);}mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());mCurrentVideoValues.put(Video.Media.DURATION, duration);getServices().getMediaSaver().addVideo(mCurrentVideoFilename,mCurrentVideoValues, mOnVideoSavedListener);logVideoCapture(duration);}mCurrentVideoValues = null;}
- 然后addVideo方法中new 了一个 AsyncTask
@Override
public void addVideo(String path, ContentValues values, OnMediaSavedListener l) {new VideoSaveTask(path, values, l, mContentResolver).execute();
}
private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =new MediaSaver.OnMediaSavedListener() {@Overridepublic void onMediaSaved(Uri uri) {if (uri != null) {mCurrentVideoUri = uri;mCurrentVideoUriFromMediaSaved = true;onVideoSaved();mActivity.notifyNewMedia(uri);}}};
private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {private String path;private final ContentValues values;private final OnMediaSavedListener listener;private final ContentResolver resolver;public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,ContentResolver r) {this.path = path;this.values = new ContentValues(values);this.listener = l;this.resolver = r;}@Overrideprotected Uri doInBackground(Void... v) {Uri uri = null;try {Uri videoTable = Uri.parse(VIDEO_BASE_URI);uri = resolver.insert(videoTable, values);String finalName = values.getAsString(Video.Media.DATA);finalName = finalName.substring(0, finalName.length() - 4);if (path.startsWith("/storage") && !path.startsWith(Storage.FLASH_DIR) && !(new File(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 = new File(finalName);if (new File(path).renameTo(finalFile)) {path = finalName;}values.put(Video.Media.DATA, finalName);resolver.update(uri, values, null, null);} catch (Exception e) {Log.e(TAG, "failed to add video to media store", e);uri = null;} finally {Log.v(TAG, "Current video URI: " + uri);}return uri;}@Overrideprotected void onPostExecute(Uri uri) {if (listener != null) {listener.onMediaSaved(uri);}}}

- notifyNewMedia方法这里可以看到里面又new了一个AsyncTask,这里直接看onPostExecute方法,上图中log的打印也可以证实这一点
@Overridepublic void notifyNewMedia(Uri uri) {if (mPaused) {return;}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(new Intent(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;}} else if (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;}new AsyncTask<FilmstripItem, Void, FilmstripItem>() {@Overrideprotected FilmstripItem doInBackground(FilmstripItem... params) {FilmstripItem data = params[0];MetadataLoader.loadMetadata(getAndroidContext(), data);return data;}@Overrideprotected void onPostExecute(final FilmstripItem data) {mDataAdapter.addOrUpdate(data);if (mCurrentModule instanceof PhotoModule ||mCurrentModule instanceof VideoModule) {AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {@Overridepublic void run() {Log.d(TAG, "generateThumbnail onPostExecute");final Optional<Bitmap> bitmap = data.generateThumbnail(mAboveFilmstripControlLayout.getWidth(),mAboveFilmstripControlLayout.getMeasuredHeight());if (bitmap.isPresent()) {indicateCapture(bitmap.get(), 0);}Log.d(TAG, "generateThumbnail onPostExecute end");}});}}}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);}VideoItem中generateThumbnail方法
@Overridepublic Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {return Optional.fromNullable(FilmstripItemUtils.loadVideoThumbnail(getData().getFilePath()));}
- 然后可以看到真正获取缩略图是通过MediaMetadataRetriever的两个方法获取的,loadVideoThumbnail 和loadVideoThumbnail end也和上面的log相对应
public static Bitmap loadVideoThumbnail(String path) {Log.d(TAG, "MediaMetadataRetriever loadVideoThumbnail");Bitmap bitmap = null;MediaMetadataRetriever retriever = new MediaMetadataRetriever();try {retriever.setDataSource(path);byte[] data = retriever.getEmbeddedPicture();if (data != null) {bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);}if (bitmap == null) {bitmap = retriever.getFrameAtTime();}} catch (Exception e) {Log.e(TAG, "MediaMetadataRetriever.setDataSource() fail:" + e.getMessage());}retriever.release();Log.d(TAG, "MediaMetadataRetriever loadVideoThumbnail end");return bitmap;}
- 然后我们找到了3399_Android10\\frameworks\\base\\media\\jni\\android_media_MediaMetadataRetriever.cpp中的getFrameAtTime方法
static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
{ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",(long long)timeUs, option, dst_width, dst_height);sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);if (retriever == 0) {jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");return NULL;}VideoFrame *videoFrame = NULL;sp<IMemory> frameMemory = retriever->getFrameAtTime方法(timeUs, option);if (frameMemory != 0) { videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());}if (videoFrame == NULL) {ALOGE("getFrameAtTime: videoFrame is a NULL pointer");return NULL;}return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType);
}
- 最后我们找到了3399_Android10\\frameworks\\av\\media\\libmediaplayerservice\\MetadataRetrieverClient.cpp文件
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;}case ROCKIT_PLAYER:{if (property_get("cts_gts.status", value, NULL)&& !strcasecmp("true", value)) {p = new StagefrightMetadataRetriever;} else {p = new RockitMetadataRetriever;}break;}default:ALOGE("player type %d is not supported", playerType);break;}if (p == NULL) {ALOGE("failed to create a retriever object");}return p;
}