RecordVideoService.java
public class RecordVideoService extends Service implements SurfaceHolder.Callback {
private static final String TAG = "RecordVideoService";
private static final boolean DEBUG = true;
private Context mContext;
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);
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);
}
}
};
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 = "RecordVideoMode";
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看是出现了死锁造成的。