在 Android 上录制视频时拍照
Taking picture while recording video on Android
我编写了如下所示的 Android 服务,用于在后台录制前置摄像头。这很好用。但现在我还想在录制时每 5 秒拍一张照片。这有可能吗?当我尝试打开第二个摄像头(在另一个服务中)时出现错误。
public class RecorderService extends Service implements SurfaceHolder.Callback {
private WindowManager windowManager;
private SurfaceView surfaceView;
private Camera camera = null;
private MediaRecorder mediaRecorder = null;
@Override
public void onCreate() {
// Create new SurfaceView, set its size to 1x1, move it to the top left corner and set this service as a callback
windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
surfaceView = new SurfaceView(this);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
1, 1,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
);
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
windowManager.addView(surfaceView, layoutParams);
surfaceView.getHolder().addCallback(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this)
//.setSmallIcon(R.mipmap.app_icon)
.setContentTitle("Background Video Recorder")
.setContentText("")
.setContentIntent(pendingIntent).build();
startForeground(MainActivity.NOTIFICATION_ID_RECORDER_SERVICE, notification);
return Service.START_NOT_STICKY;
}
// Method called right after Surface created (initializing and starting MediaRecorder)
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
camera = Camera.open(1);
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_720P));
FileUtil.createDir("/storage/emulated/0/Study/Camera");
mediaRecorder.setOutputFile("/storage/emulated/0/Study/Camera/" + Long.toString(System.currentTimeMillis()) + ".mp4");
try { mediaRecorder.prepare(); } catch (Exception e) {}
mediaRecorder.start();
try {
camera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
e.printStackTrace();
}
Runnable runnable = new PictureThread(camera);
Thread thread = new Thread(runnable);
thread.start();
}
// Stop recording and remove SurfaceView
@Override
public void onDestroy() {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
camera.lock();
camera.release();
windowManager.removeView(surfaceView);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
@Override
public IBinder onBind(Intent intent) { return null; }
}
编辑:我现在写了一个帖子PictureThread
。本帖从RecorderService
开始,尝试在录像的同时拍照。
public class PictureThread implements Runnable {
private final static String TAG = PictureThread.class.getSimpleName();
private Camera camera;
PictureThread(Camera camera) {
this.camera = camera;
}
@Override
public void run() {
camera.startPreview();
camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}
Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
}
};
Camera.PictureCallback rawCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
}
};
Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
Log.i(TAG, "onPictureTaken - jpeg");
}
};
}
不幸的是 jpegCallback
从未被调用(即从未打印日志消息)。当我打开平板电脑的相机应用程序时,我可以在录像的同时拍照,所以这应该是可以的。
我还尝试了 Alex Cohn (https://github.com/mobapptuts/android_camera2_api_video_app) 建议的 Camera2 API 示例。录制视频和拍照都可以,但是当我尝试在录制时拍照时,没有创建照片(但也没有错误)。尽管如此,我发现这个示例应用程序运行起来不是很可靠(也许还有另一个示例应用程序)。
编辑 2:takePicture
的 shutterCallback
和 rawCallback
被调用,但 rawCallback
的数据为空. jpegCallback
从未被调用过。知道为什么以及如何解决这个问题吗?我还尝试在线程中等待一段时间以提供回调时间以供调用,并且我尝试在我的 main activity 中使回调静态化(这样它就不会被垃圾收集)。什么都没用。
不可以,您不能为视频录制和静态捕捉打开单独的相机实例。已弃用的相机 API 对于此类任务不可靠(参见例如 Android camera parameter IsVideoSnapshotSupported incorrectly set to false 关于三星 S4)。
您可以使用 camera2 API(在支持这种模式的设备上)从同一相机实例捕获不同的格式和分辨率。这里有一个视频教程:https://www.nigeapptuts.com/android-video-app-still-capture-recording/
编辑:
随着澄清:
旧摄像头API支持在录制视频时调用takePicture(),如果Camera.Parameters.isVideoSnapshotSupported在设备上报真有问题。
只需保留要传递到 MediaRecorder 的同一个相机实例,然后对其调用 Camera.takePicture()。
Camera2 还通过创建同时具有预览、录制和 JPEG 输出的会话来更灵活地支持这一点。
原回答:
如果你的意思是用后置摄像头拍照,同时用前置摄像头录制 - 这取决于设备。一些设备有足够的硬件资源来同时 运行 多个摄像头,但大多数设备不会(它们在两个摄像头之间共享处理硬件)。
判断是否可以同时使用多个摄像头的唯一方法是在一个摄像头已打开时尝试打开第二个摄像头。如果它有效,你应该可以开始了;如果不是,则该设备不支持同时使用多个摄像头。
我编写了如下所示的 Android 服务,用于在后台录制前置摄像头。这很好用。但现在我还想在录制时每 5 秒拍一张照片。这有可能吗?当我尝试打开第二个摄像头(在另一个服务中)时出现错误。
public class RecorderService extends Service implements SurfaceHolder.Callback {
private WindowManager windowManager;
private SurfaceView surfaceView;
private Camera camera = null;
private MediaRecorder mediaRecorder = null;
@Override
public void onCreate() {
// Create new SurfaceView, set its size to 1x1, move it to the top left corner and set this service as a callback
windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
surfaceView = new SurfaceView(this);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
1, 1,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
);
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
windowManager.addView(surfaceView, layoutParams);
surfaceView.getHolder().addCallback(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this)
//.setSmallIcon(R.mipmap.app_icon)
.setContentTitle("Background Video Recorder")
.setContentText("")
.setContentIntent(pendingIntent).build();
startForeground(MainActivity.NOTIFICATION_ID_RECORDER_SERVICE, notification);
return Service.START_NOT_STICKY;
}
// Method called right after Surface created (initializing and starting MediaRecorder)
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
camera = Camera.open(1);
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_720P));
FileUtil.createDir("/storage/emulated/0/Study/Camera");
mediaRecorder.setOutputFile("/storage/emulated/0/Study/Camera/" + Long.toString(System.currentTimeMillis()) + ".mp4");
try { mediaRecorder.prepare(); } catch (Exception e) {}
mediaRecorder.start();
try {
camera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
e.printStackTrace();
}
Runnable runnable = new PictureThread(camera);
Thread thread = new Thread(runnable);
thread.start();
}
// Stop recording and remove SurfaceView
@Override
public void onDestroy() {
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
camera.lock();
camera.release();
windowManager.removeView(surfaceView);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
@Override
public IBinder onBind(Intent intent) { return null; }
}
编辑:我现在写了一个帖子PictureThread
。本帖从RecorderService
开始,尝试在录像的同时拍照。
public class PictureThread implements Runnable {
private final static String TAG = PictureThread.class.getSimpleName();
private Camera camera;
PictureThread(Camera camera) {
this.camera = camera;
}
@Override
public void run() {
camera.startPreview();
camera.takePicture(shutterCallback, rawCallback, jpegCallback);
}
Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
}
};
Camera.PictureCallback rawCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
}
};
Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
Log.i(TAG, "onPictureTaken - jpeg");
}
};
}
不幸的是 jpegCallback
从未被调用(即从未打印日志消息)。当我打开平板电脑的相机应用程序时,我可以在录像的同时拍照,所以这应该是可以的。
我还尝试了 Alex Cohn (https://github.com/mobapptuts/android_camera2_api_video_app) 建议的 Camera2 API 示例。录制视频和拍照都可以,但是当我尝试在录制时拍照时,没有创建照片(但也没有错误)。尽管如此,我发现这个示例应用程序运行起来不是很可靠(也许还有另一个示例应用程序)。
编辑 2:takePicture
的 shutterCallback
和 rawCallback
被调用,但 rawCallback
的数据为空. jpegCallback
从未被调用过。知道为什么以及如何解决这个问题吗?我还尝试在线程中等待一段时间以提供回调时间以供调用,并且我尝试在我的 main activity 中使回调静态化(这样它就不会被垃圾收集)。什么都没用。
不可以,您不能为视频录制和静态捕捉打开单独的相机实例。已弃用的相机 API 对于此类任务不可靠(参见例如 Android camera parameter IsVideoSnapshotSupported incorrectly set to false 关于三星 S4)。
您可以使用 camera2 API(在支持这种模式的设备上)从同一相机实例捕获不同的格式和分辨率。这里有一个视频教程:https://www.nigeapptuts.com/android-video-app-still-capture-recording/
编辑:
随着澄清:
旧摄像头API支持在录制视频时调用takePicture(),如果Camera.Parameters.isVideoSnapshotSupported在设备上报真有问题。
只需保留要传递到 MediaRecorder 的同一个相机实例,然后对其调用 Camera.takePicture()。
Camera2 还通过创建同时具有预览、录制和 JPEG 输出的会话来更灵活地支持这一点。
原回答:
如果你的意思是用后置摄像头拍照,同时用前置摄像头录制 - 这取决于设备。一些设备有足够的硬件资源来同时 运行 多个摄像头,但大多数设备不会(它们在两个摄像头之间共享处理硬件)。
判断是否可以同时使用多个摄像头的唯一方法是在一个摄像头已打开时尝试打开第二个摄像头。如果它有效,你应该可以开始了;如果不是,则该设备不支持同时使用多个摄像头。