I have been trying to create a background video recoder using Service
class and using Camera API
. When I run the app, everything seems to be working fine. However, when I stop the Service, my app throws ANR. Using DDMS tool
, I found following info about my main
thread after I press stop recording button:
at android.media.MediaRecorder.stop(Native Method)
at com.svtech.thirdeye.thirdeye.Services.VideoRecordingOldApiService.stopRecording(VideoRecordingOldApiService.java:212)
at com.svtech.thirdeye.thirdeye.Services.VideoRecordingOldApiService.onDestroy(VideoRecordingOldApiService.java:250)
at android.app.ActivityThread.handleStopService(ActivityThread.java:2877)
at android.app.ActivityThread.access$2000(ActivityThread.java:162)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1466)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5371)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)
The onDestroy()
method code of my Service
class is as follows:
@Override
public void onDestroy() {
super.onDestroy();
if (linearLayout != null) {
windowManager.removeView(linearLayout);
linearLayout = null;
}
mMediaRecorder.stop();
if (mMediaRecorder != null) {
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
}
if (mCamera != null) {
mCamera.release();
mCamera.lock();
mCamera = null;
}
}
EDITS: Rectified Service
Class
public class VideoRecordingOldApiService extends Service implements SurfaceHolder.Callback {
private Camera mCamera;
private String outputFile;
private MediaRecorder mMediaRecorder;
private CameraCheck cameraCheck;
private final static String TAG = "VideoRecorderService";
private LinearLayout linearLayout;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
private WindowManager windowManager;
private Thread thread;
private Handler handler;
public VideoRecordingOldApiService() {
}
@Override
public void onCreate() {
linearLayout = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.surfaceview_layout, null);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
getResources().getDimensionPixelSize(R.dimen.textureView_width), getResources().getDimensionPixelSize(R.dimen.textureView_height)));
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.OPAQUE
);
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
windowManager.addView(linearLayout, layoutParams);
surfaceView = (SurfaceView) linearLayout.findViewById(R.id.videoSurfaceView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
cameraCheck = new CameraCheck();
handler = new Handler();
//Setup Notification
final Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
notificationIntent.addCategory("android.intent.category.LAUNCHER");
final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification;
notification = new Notification.Builder(getApplicationContext())
.setSmallIcon(R.drawable.videorecordicon)
.setOngoing(true)
.setPriority(Notification.PRIORITY_DEFAULT)
.setContentTitle(getResources().getString(R.string.video_recorder_notification))
.setContentText(getResources().getString(R.string.notification_video_text) + "...")
.setContentIntent(pendingIntent).build();
startForeground(1, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (initCamera()) {
thread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
if (initRecorder()) {
mMediaRecorder.start();
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), R.string.video_recorder_started, Toast.LENGTH_LONG).show();
}
});
} else {
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Couldn't start MediaRecorder", Toast.LENGTH_LONG).show();
}
});
}
}
});
thread.start();
} else {
Toast.makeText(getApplicationContext(), "Camera not found", Toast.LENGTH_SHORT).show();
}
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
public String getFileName() {
String fileName = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ "/" + "video_record" + System.currentTimeMillis() + ".mp4";
Log.i("Camera Recorder", fileName);
} else {
Toast.makeText(getApplicationContext(), "No External Storage Found", Toast.LENGTH_LONG).show();
}
return fileName;
}
private boolean initCamera() {
try {
mCamera = cameraCheck.getDesiredCamera(this, Camera.CameraInfo.CAMERA_FACING_BACK);
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRecordingHint(true);
Camera.Size previewSize = getOptimalPreviewSize(parameters.getSupportedPreviewSizes(),
surfaceView.getWidth(), surfaceView.getHeight());
parameters.setPreviewSize(previewSize.width, previewSize.height);
mCamera.setDisplayOrientation(90);
} catch (Exception e) {
Log.v(TAG, "Could not initialise the camera");
e.printStackTrace();
return false;
}
return true;
}
private boolean initRecorder() {
mMediaRecorder = new MediaRecorder();
outputFile = getFileName();
CamcorderProfile profile = CamcorderProfile.get(Camera.CameraInfo.CAMERA_FACING_BACK, CamcorderProfile.QUALITY_HIGH);
profile.videoFrameWidth = mCamera.getParameters().getSupportedVideoSizes().get(1).width;
profile.videoFrameHeight = mCamera.getParameters().getSupportedVideoSizes().get(1).height;
profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
profile.videoCodec = MediaRecorder.VideoEncoder.H264;
try {
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(profile);
mMediaRecorder.setOutputFile(outputFile);
//mMediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
mMediaRecorder.prepare();
Log.v(TAG, "MediaRecorder initialized successfully");
} catch (Exception e) {
Log.v(TAG, "MediaRecorder failed to initialize");
e.printStackTrace();
return false;
}
return true;
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.lock();
mCamera.release();
mCamera = null;
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (!thread.isInterrupted()) {
thread.interrupt();
Toast.makeText(getApplicationContext(), R.string.recording_done, Toast.LENGTH_LONG).show();
}
if (linearLayout != null) {
windowManager.removeView(linearLayout);
linearLayout = null;
}
stopForeground(true);
releaseCamera();
thread = null;
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
Can someone help me figure out why I am getting ANR on onStop()
method of Mediarecoder?? And what does DDMS tool indicate about main
thread???
Lifecycle callbacks of Service
are always called on the main thread of your app. A Service
runs in the "background" only from a user perception standpoint. If you want true background operations, you would need to use your own thread(s). The call to MediaRecorder.stop()
is a blocking call as it is talking to the media server process, telling it to stop operations. This is taking too long (other main thread ops are blocked waiting) and the system declares ANR for your app and kills it.