新增一個系統服務在後臺錄製視訊,並儲存到本地
RecordVideoService.java
public class RecordVideoService extends Service implements SurfaceHolder.Callback { private static final String TAG = "mylog"; private static final boolean DEBUG = true; private Context mContext; private static final String ACION_RECORD_START = "com.android.record.start"; private static final String ACION_RECORD_STOP = "com.android.record.stop"; private RecordVideoServiceImp mRecordVideoServiceImp; private RecordVideoMode mRecordVideoMode; LinearLayout mLinearLayout; private WindowManager mWindowManager; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private static final int MSG_START_RECORD = 1 << 0; private static final int MSG_STOP_RECORD = 1 << 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { int what = msg.what; if (DEBUG) Log.d(TAG, "handleMessage what : " + what); switch (what) { case MSG_START_RECORD: startRecordVideo(true); break; case MSG_STOP_RECORD: startRecordVideo(false); break; default: break; } } };
@Override public void onCreate() { super.onCreate(); mContext = this; mRecordVideoServiceImp = new RecordVideoServiceImp(); publish(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SHUTDOWN); intentFilter.addAction(ACION_RECORD_START); intentFilter.addAction(ACION_RECORD_STOP); mContext.registerReceiver(mReceiver, intentFilter); initView(); mRecordVideoMode = new RecordVideoMode(mContext, mSurfaceHolder); }
@Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); }
@Override public IBinder onBind(Intent intent) { return mRecordVideoServiceImp; }
@Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); }
@Override public void onDestroy() { super.onDestroy(); if (null != mRecordVideoMode) mRecordVideoMode.releaseVideoRecorder(); unregisterReceiver(mReceiver); releaseView(); }
private void publish() { Log.d(TAG, "publish: " + mRecordVideoServiceImp); ServiceManager.addService("recordvideo", mRecordVideoServiceImp); }
private final class RecordVideoServiceImp extends IRecordVideoService.Stub { @Override public void startRecordVideo() throws RemoteException { if (DEBUG) Log.w(TAG, "startRecordVideo"); mHandler.sendEmptyMessage(MSG_START_RECORD); }
@Override public void stopRecordVideo() throws RemoteException { if (DEBUG) Log.w(TAG, "stopRecordVideo"); mHandler.sendEmptyMessage(MSG_STOP_RECORD); } }
private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (DEBUG) Log.w(TAG, "BroadcastReceiver action : " + action); if (Intent.ACTION_SHUTDOWN.equals(action)) { mHandler.sendEmptyMessage(MSG_STOP_RECORD); } else if (ACION_RECORD_START.equals(action)) { mHandler.sendEmptyMessage(MSG_START_RECORD); } else if (ACION_RECORD_STOP.equals(action)) { mHandler.sendEmptyMessage(MSG_STOP_RECORD); } } };
private void initView() { Log.d(TAG, "initView");
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(); mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; // 設定圖片格式,效果為背景透明 //wmParams.format = PixelFormat.RGBA_8888; mLayoutParams.format = 1; mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; // 以螢幕左上角為原點,設定x、y初始值 mLayoutParams.x = 0; mLayoutParams.y = 0;
mSurfaceView = new SurfaceView(this); mSurfaceHolder = mSurfaceView.getHolder(); WindowManager.LayoutParams mLayoutParamsSur = new WindowManager.LayoutParams(); mLayoutParamsSur.width = 1; mLayoutParamsSur.height = 1; mLayoutParamsSur.alpha = 255; mSurfaceView.setLayoutParams(mLayoutParamsSur); mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceView.getHolder().addCallback(this);
mLinearLayout = new LinearLayout(this); WindowManager.LayoutParams mLayoutParamsLin = new WindowManager.LayoutParams(); mLayoutParamsLin.width = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParamsLin.height = WindowManager.LayoutParams.WRAP_CONTENT; mLinearLayout.setLayoutParams(mLayoutParamsLin); mLinearLayout.addView(mSurfaceView);
mWindowManager.addView(mLinearLayout, mLayoutParams); // 建立View }
private void releaseView() { if (mWindowManager != null) { mWindowManager.removeView(mLinearLayout); } }
@Override public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "surfaceCreated"); mSurfaceHolder = holder; if (null == mRecordVideoMode) { mRecordVideoMode = new RecordVideoMode(mContext, mSurfaceHolder); } mRecordVideoMode.updateSurfaceHoler(mSurfaceHolder); mHandler.sendEmptyMessage(MSG_START_RECORD);
}
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d(TAG, "surfaceChanged"); mSurfaceHolder = holder; if (null == mRecordVideoMode) { mRecordVideoMode = new RecordVideoMode(mContext, mSurfaceHolder); } mRecordVideoMode.updateSurfaceHoler(mSurfaceHolder); }
@Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "surfaceDestroyed"); }
private Runnable mStartRecordRunable = new Runnable() { @Override public void run() { if (null != mRecordVideoMode) { mRecordVideoMode.startVideoRecording(); } } };
private Runnable mStopRecordRunable = new Runnable() { @Override public void run() { if (null != mRecordVideoMode) { mRecordVideoMode.releaseVideoRecorder(); } } };
private void startRecordVideo(boolean enable) { if (DEBUG) Log.d(TAG, "startRecordVideo enable : " + enable); if (enable) { mHandler.removeCallbacks(mStartRecordRunable); mHandler.post(mStartRecordRunable); } else { mHandler.removeCallbacks(mStopRecordRunable); mHandler.post(mStopRecordRunable); } } }
---------------------------------------------------------------------------------------
RecordVideoMode.java
public class RecordVideoMode { private static final String TAG = "mylog"; private static final boolean DEBUG = true; private static final Long VIDEO_4G_SIZE = 4 * 1024 * 1024 * 1024L; private static final int NOT_FAT_FILE_SYSTEM = 0; private MediaRecorder mMediaRecorder; private Camera mCamera; protected boolean mIsRecorderCameraReleased = true; private Context mContext; private static final long RECORD_LOW_STORAGE_THRESHOLD = 9600000; private static StorageManager sStorageManager; private static final long UNAVAILABLE = -1L; private static final long PREPARING = -2L; private static final long UNKNOWN_SIZE = -3L; private static final long FULL_SDCARD = -4L; private static final String FOLDER_PATH = "/RecordVideo"; private static final String OUTPUT_FORMAT = ".3gp"; private static String sMountPoint; protected static final int MEDIA_RECORDER_INFO_BITRATE_ADJUSTED = 898; protected static final int MEDIA_RECORDER_INFO_RECORDING_SIZE = 895; protected static final int MEDIA_RECORDER_INFO_FPS_ADJUSTED = 897; protected static final int MEDIA_RECORDER_INFO_START_TIMER = 1998; protected static final int MEDIA_RECORDER_INFO_WRITE_SLOW = 899; protected static final int MEDIA_RECORDER_INFO_CAMERA_RELEASE = 1999; protected static final int MEDIA_RECORDER_ENCODER_ERROR = -1103; private static final String RECORDER_INFO_SUFFIX = "media-recorder-info="; SurfaceHolder mSurfaceHolder; private Toast mToast; private boolean mRelease = false; private boolean mStartRecording = false; private int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
public RecordVideoMode(Context context, SurfaceHolder surfaceHolder) { mContext = context; mSurfaceHolder = surfaceHolder; sMountPoint = StorageManagerEx.getDefaultPath(); }
public void updateSurfaceHoler(SurfaceHolder surfaceHolder) { mSurfaceHolder = surfaceHolder; }
public int startVideoRecording() { if (DEBUG) Log.d(TAG, "startVideoRecording"); if (!mIsRecorderCameraReleased) { if (DEBUG) Log.d(TAG, "startVideoRecording videoRecord has been start"); return -1; }
if (mStartRecording) { if (DEBUG) Log.d(TAG, "startVideoRecording videoRecord is opening"); showToast("videoRecord is opening"); return -1; }
mStartRecording = true; if (null == mCamera) { if (DEBUG) Log.d(TAG, "startVideoRecording mCamera open"); try { int numCameras = Camera.getNumberOfCameras(); if (numCameras > 1) { mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); } else if (numCameras == 1){ mCamera = Camera.open(); } else { if (DEBUG) Log.i(TAG, "startVideoRecording numberOfCameras : " + numCameras); showToast("Camera not supported"); mStartRecording = false; return -1; }
} catch (Exception e) { Log.e(TAG, "Camera open failed, exception : " + e); showToast("Camera open failed"); } }
if (null == mCamera) { if (DEBUG) Log.d(TAG, "startVideoRecording mCamera is null"); mStartRecording = false; return -1; }
Camera.Parameters parameters = mCamera.getParameters(); List<String> focusModesList = parameters.getSupportedFocusModes();
if (focusModesList.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } else if (focusModesList.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); }
mCamera.setParameters(parameters); mCamera.setDisplayOrientation(90); if (mMediaRecorder == null) { mMediaRecorder = new MediaRecorder(); } mCamera.stopPreview(); mCamera.unlock(); mMediaRecorder.setCamera(mCamera);
CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mMediaRecorder.setOutputFormat(mProfile.fileFormat); mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate); mMediaRecorder.setVideoEncoder(mProfile.videoCodec); mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight); mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate); mMediaRecorder.setAudioEncodingBitRate(mProfile.audioBitRate); mMediaRecorder.setAudioChannels(mProfile.audioChannels); mMediaRecorder.setAudioSamplingRate(mProfile.audioSampleRate); mMediaRecorder.setAudioEncoder(mProfile.audioCodec); mMediaRecorder.setMaxDuration(0);//disables the duration limit
/*try { mMediaRecorder.setMaxFileSize(getRecorderMaxSize(1)); } catch (RuntimeException exception) { if (DEBUG) Slog.w(TAG, "initializeNormalRecorder()", exception); }*/ mMediaRecorder.setPreviewDisplay( mSurfaceHolder.getSurface() ;//必須要設,並且不能設null,否則在mMediaRecorder.start()是拋異常 mMediaRecorder.setOutputFile(getFileDirectory() + File.separator + createVideoName() + OUTPUT_FORMAT); setMediaRecorderParameters(mMediaRecorder); int orientation = mContext.getResources().getConfiguration().orientation; mMediaRecorder.setOrientationHint(getRecordingRotation(orientation, mCameraId));
try { mMediaRecorder.prepare(); mMediaRecorder.start(); mIsRecorderCameraReleased = false; } catch (Exception e) { if (DEBUG) Log.e(TAG, "startVideoRecording exception " + e); showToast("MediaRecorder start failed"); releaseVideoRecorder(); mStartRecording = false; return -1; } mMediaRecorder.setOnErrorListener(mRecorderErrorListener); mMediaRecorder.setOnInfoListener(mRecorderOnInfoListener); mMediaRecorder.setOnCameraReleasedListener(mRecorderOnInfoListener); mStartRecording = false;
return 1; }
public void releaseVideoRecorder() { if (DEBUG) Log.d(TAG, "releaseMediaRecorder mIsRecorderCameraReleased : " + mIsRecorderCameraReleased); if (mIsRecorderCameraReleased) { if (DEBUG) Log.d(TAG, "releaseMediaRecorder return when camera&mediaRecorder has been released"); return; }
if (mRelease) { if (DEBUG) Log.d(TAG, "releaseMediaRecorder Return when MediaRecorder is being released"); return; }
if (mMediaRecorder != null) { mRelease = true; try { if (!mIsRecorderCameraReleased) { mMediaRecorder.stop(); } mMediaRecorder.reset(); mMediaRecorder.release(); if (null != mCamera) { mCamera.release(); } } catch (Exception e) { if (DEBUG) Log.e(TAG, "releaseMediaRecorder exception : " + e); } mIsRecorderCameraReleased = true; mMediaRecorder.setOnInfoListener(null); mMediaRecorder.setOnErrorListener(null); mMediaRecorder.setOnCameraReleasedListener(null); mRelease = false; mMediaRecorder = null; mCamera = null; } mStartRecording = false; }
private long getRecorderMaxSize(long limitSize) {
long maxFileSize = getAvailableSpace() - RECORD_LOW_STORAGE_THRESHOLD; if (DEBUG) Log.w(TAG, "getRecorderMaxSize spaceSize : " + maxFileSize); if (limitSize > 0 && limitSize < maxFileSize) { maxFileSize = limitSize; } else if (maxFileSize >= VIDEO_4G_SIZE && NOT_FAT_FILE_SYSTEM != getStorageCapbility()) { maxFileSize = VIDEO_4G_SIZE; } if (DEBUG) Log.w(TAG, "getRecorderMaxSize maxFileSize : " + maxFileSize); return maxFileSize; }
public static long getAvailableSpace() { String state; StorageManager storageManager = getStorageManager(); state = storageManager.getVolumeState(sMountPoint); // Log.d(TAG, "External storage state=" + state + ", mount point = " + // sMountPoint); if (Environment.MEDIA_CHECKING.equals(state)) { return PREPARING; } if (!Environment.MEDIA_MOUNTED.equals(state)) { return UNAVAILABLE; }
File dir = new File(getFileDirectory()); dir.mkdirs(); boolean isDirectory = dir.isDirectory(); boolean canWrite = dir.canWrite(); if (!isDirectory || !canWrite) { if (DEBUG) Slog.d(TAG, "getAvailableSpace() isDirectory=" + isDirectory + ", canWrite=" + canWrite); return FULL_SDCARD; }
try { // Here just use one directory to stat fs. StatFs stat = new StatFs(getFileDirectory()); return stat.getAvailableBlocks() * (long) stat.getBlockSize(); } catch (Exception e) { if (DEBUG) Slog.e(TAG, "Fail to access external storage", e); } return UNKNOWN_SIZE; }
private static StorageManager getStorageManager() { if (sStorageManager == null) { try { sStorageManager = new StorageManager(null, null); } catch (Exception e) { e.printStackTrace(); } } return sStorageManager; }
public static String getFileDirectory() { if (TextUtils.isEmpty(sMountPoint)) { sMountPoint = "/sdcard"; } String path = sMountPoint + FOLDER_PATH; File dir = new File(path); if (!dir.exists()) { dir.mkdir(); } if (DEBUG) Log.d(TAG, "getFileDirectory path : " + path); return path; }
public static Long getStorageCapbility() { StorageManager storageManager = getStorageManager(); String storagePath = sMountPoint;// storageManager.getDefaultPath(); StorageVolume[] volumes = storageManager.getVolumeList(); int nVolume = -1; if (volumes != null) { for (int i = 0; i < volumes.length; i++) { if (volumes[i].getPath().equals(storagePath)) { nVolume = i; break; } } Long maxFileSize = 0l; if (nVolume != -1) { maxFileSize = volumes[nVolume].getMaxFileSize(); if (DEBUG) Slog.i(TAG, "getStorageCapbility maxFileSize = " + maxFileSize + ",nVolume = " + nVolume); } return maxFileSize; } else { return 0l; } }
private String createVideoName() { String nameStr; SimpleDateFormat mFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); Date date = new Date(); nameStr = mFormat.format(date); if (DEBUG) Log.d(TAG, "createVideoName nameStr : " + nameStr); return nameStr; }
private MediaRecorder.OnErrorListener mRecorderErrorListener = new MediaRecorder.OnErrorListener() { @Override public void onError(MediaRecorder mr, int what, int extra) { if (DEBUG) Log.d(TAG, "OnErrorListener what : " + what + " extra : " + extra); if (MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN == what || MEDIA_RECORDER_ENCODER_ERROR == extra) { // We may have run out of space on the SD card. releaseVideoRecorder(); } } };
private MediaRecorder.OnInfoListener mRecorderOnInfoListener = new MediaRecorder.OnInfoListener() { @Override public void onInfo(MediaRecorder mr, int what, int extra) { if (DEBUG) Log.d(TAG, "OnInfoListener what : " + what); switch (what) { case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: showToast("max duration reached"); if (DEBUG) Log.d(TAG, "OnInfoListener MAX_DURATION_REACHED"); releaseVideoRecorder(); break; case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED: showToast("Size limit reached"); if (DEBUG) Log.d(TAG, "OnInfoListener MAX_FILESIZE_REACHED"); releaseVideoRecorder(); break; case MEDIA_RECORDER_INFO_CAMERA_RELEASE: if (DEBUG) Log.d(TAG, "OnInfoListener CAMERA_RELEASE"); releaseVideoRecorder(); break; case MEDIA_RECORDER_INFO_START_TIMER: break; case MEDIA_RECORDER_INFO_FPS_ADJUSTED: case MEDIA_RECORDER_INFO_BITRATE_ADJUSTED: showToast("Low memory,auto change quality"); if (DEBUG) Log.d(TAG, "OnInfoListener auto change quality"); break; case MEDIA_RECORDER_INFO_WRITE_SLOW: showToast("Low memory,auto stop recording"); if (DEBUG) Log.d(TAG, "OnInfoListener MEDIA_RECORDER_INFO_WRITE_SLOW"); releaseVideoRecorder(); break; case MEDIA_RECORDER_INFO_RECORDING_SIZE: break; default: break; } } };
private void setMediaRecorderParameters(MediaRecorder mediaRecorder) { try { mediaRecorder.setParametersExtra(RECORDER_INFO_SUFFIX + MEDIA_RECORDER_INFO_BITRATE_ADJUSTED); mediaRecorder.setParametersExtra(RECORDER_INFO_SUFFIX + MEDIA_RECORDER_INFO_FPS_ADJUSTED); mediaRecorder.setParametersExtra(RECORDER_INFO_SUFFIX + MEDIA_RECORDER_INFO_START_TIMER); mediaRecorder.setParametersExtra(RECORDER_INFO_SUFFIX + MEDIA_RECORDER_INFO_WRITE_SLOW); mediaRecorder.setParametersExtra(RECORDER_INFO_SUFFIX + MEDIA_RECORDER_INFO_CAMERA_RELEASE);
} catch (Exception ex) { ex.printStackTrace(); } }
public void showToast(String text) { if (null != mToast) { mToast.cancel(); } mToast = Toast.makeText(mContext, text, Toast.LENGTH_SHORT); mToast.show(); }
public static int getRecordingRotation(int orientation, int cameraId) { int rotation = 90; if (DEBUG) Log.d(TAG, "getRecordingRotation orientation : " + orientation); boolean backCamera = cameraId == Camera.CameraInfo.CAMERA_FACING_BACK; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { if (backCamera) { rotation = 0; } else { rotation = 180; } } else if (orientation == Configuration.ORIENTATION_PORTRAIT){ if (backCamera) { rotation = 90; } else { rotation = 270; } } if (DEBUG) Log.d(TAG, "getRecordingRotation rotation : " + rotation); return rotation; } }
--------------------------------------------------------------------------------------------------
IRecordVideoService.aidl
interface IRecordVideoService { void startRecordVideo(); void stopRecordVideo(); }
--------------------------------------------------------------------------------------------
android7.0後臺新增服務到系統需要新增如下selinux許可權
移植該部分功能需新增到selinux許可權如下所示: device/mediatek/common/sepolicy/basic/service.te +type record_video_service, service_manager_type;
device/mediatek/common/sepolicy/basic/service_contexts +recordvideo u:object_r:record_video_service:s0
device/mediatek/common/sepolicy/basic/system_app.te +allow system_app record_video_service:service_manager {add find};
device/mediatek/common/sepolicy/basic/system_server.te +allow system_server record_video_service:service_manager { add find };
------------------------------------------------------------------------------------------------
將aidl新增到apk中編譯Android.mk LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + src/com/android/recordvideo/IRecordVideoService.aidl
----------------------------------------------------------------------------------------------------
AndroidManifest.xml中新增到配置如下: + <uses-permission android:name="android.permission.CAMERA"/> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <uses-feature android:name="android.hardware.camera" android:required="true"/> + <uses-feature android:name="android.hardware.camera.autofocus" android:required="true" /> +
+ <service android:name="com.android.recordvideo.RecordVideoService"> + <intent-filter> + <action android:name="com.android.action.RECORD_VIDEO"/> + </intent-filter> + </service> + + <receiver android:name="com.android.recordvideo.RecordVideoBootReceiver"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + </receiver>
注:新增一個系統服務,沒有按照系統服務那樣直接extends SystemService,是因為直接在SystemService中呼叫Camera.ope()時不能正常開啟,從log看是出現了死鎖造成的。