The cwac-camera library has event hooks that are called before a photo is saved:
@Override public void saveImage(PictureTransaction xact, byte[] image) {}
Have I just overlooked this or is there no sink which tells me when a video file has been saved?
Some more background info: There are two ways to end a video. One is by calling
.stopRecording();
the other is by configuring the recorder for a max duration/max filesize (in which case recording stops without the explicit call to stopRecording():
recorder.setMaxDuration()
In both cases, as it looks to me, Android needs about 100 to 200 msec to finalize the file. As the video file is often corrupted when I just use
mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override public void onInfo(MediaRecorder mr, int what, int extra) {}
}
to detect when it is finished or try reading it immediately after the call to stopRecording(). I tried using FileObserver to detect when media recorder actually closes the file, which works well.
I'd like to discuss this issue, if anybody has had the same experience, and if there is a way how cwac-camera detects and signals a recorded file.
--
I just stepped over this in Android documentation here http://developer.android.com/reference/android/media/MediaRecorder.html#setMaxDuration(int)
"Stopping happens asynchronously, there is no guarantee that the recorder will have stopped by the time the listener is notified."
This explains why the file sometimes isn't properly closed and it implicitly means that using FileObserver probably is the only safe and working way to access the file after it has been written.
So we need something like this to detect the event.
@Override protected File getVideoPath() {
File file = super.getVideoPath(); // assemble the video path
fileObserverPath = file.getAbsolutePath();
fileObserver = new FileObserver(fileObserverPath, FileObserver.CLOSE_WRITE) {
@Override public void onEvent(int event, String videoPath) {
System.out.println("**** CameraHost: write closed");
if (fileObserver != null) { // defensive
fileObserver.stopWatching();
fileObserver = null;
}
// process the file here
}
};
fileObserver.startWatching();
return file;
}
--
I went ahead and added the following code to my CameraHost to get the timings:
@Override public void configureRecorderOutput(int cameraId, MediaRecorder recorder) {
System.out.println("**** CameraHost: configureRecorderOutput");
recorder.setMaxDuration(Constants.FRAMEPLAYER_MAX_RECORDING_LENGTH_PER_FRAME_MSEC);
recorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override public void onInfo(MediaRecorder mr, int what, int extra) {
switch (what) {
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED: {
System.out.println("**** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");
break;
}
}
}
});
super.configureRecorderOutput(cameraId, recorder);
}
Timings:
11-20 07:33:28.974 32602-32602/cc.closeup.android I/System.out﹕ **** CameraHost: configureRecorderOutput
11-20 07:33:31.064 32602-32602/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 07:33:31.084 32602-32602/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 07:33:36.914 32602-32655/cc.closeup.android I/System.out﹕ **** CameraHost: write closed
11-20 07:33:36.914 32602-32655/cc.closeup.android I/System.out﹕ **** CameraHost: write closed
Two things that are strange:
- MEDIA_RECORDER_INFO_MAX_DURATION_REACHED is raised, but the recording does not stop. Contrary to what is specified in the Android documentation, I must explicitly call stop.
- Even though configureRecorderOutput is only raised once, my listeners are called twice. I tried the same outside cwac camera and there listeners are only called once.
Not sure if I made a mistake somewhere or something goes wrong in cwac-camera.
Anybody there to comment?
--
Regarding 2., whatever that is, my log reveals that the media recorder which fires the event is the same, and short after what=800 (MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) an undocumented what=536871912 is raised. The second time the event is raised the undocumented what=268436456.
[from Asus Memo Pad 7:]
11-20 09:41:25.748 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@41c9da88, what = 800, extra 0
11-20 09:41:25.748 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 09:41:25.748 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@41c9da88, what = 536871912, extra 0
11-20 09:41:25.778 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@41c9da88, what = 800, extra 0
11-20 09:41:25.778 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 09:41:25.778 3398-3398/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@41c9da88, what = 268436456, extra 0
I ran this on another device, where the MEDIA_RECORDER_INFO_MAX_DURATION_REACHED event is also raised twice, but the undocumented what=? is not raised at all. Vendor specific implementation?
[from Samsung Galaxy Note 2:]
11-20 09:46:42.711 25699-25699/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@42831c30, what = 800, extra 0
11-20 09:46:42.711 25699-25699/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 09:46:42.776 25699-25699/cc.closeup.android I/System.out﹕ **** CameraHost: onInfo: mr = android.media.MediaRecorder@42831c30, what = 800, extra 0
11-20 09:46:42.776 25699-25699/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
I keep posting...
--
Regarding 1., recording is actually stopped (the recorded file is one second long), but FileObserver() is obviously only called much later, namely after I explicitly call cameraView.stop().
I tried to call stop() directly on the MediaRecorder passed in the event, which works in Android but not in cwac.
--
Regarding 1., I digged into this further and it looks like the video file is kept written until I explicitly call cameraView.stop() - only then is it truncated to one second and closed.
// startRecording() called here
11-20 11:29:49.072 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.072 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 32, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:49.082 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.132 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.132 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.132 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.132 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.132 4987-4987/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 11:29:51.142 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.142 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.142 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.142 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.142 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
(...)
11-20 11:29:51.182 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.182 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.182 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.192 4987-4987/cc.closeup.android I/System.out﹕ **** CameraHost: MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
11-20 11:29:51.192 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.192 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.192 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.192 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.192 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
(...)
11-20 11:29:51.252 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.262 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.262 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.262 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.262 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:51.262 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
// stopRecording() called here
11-20 11:29:52.492 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.492 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.492 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.492 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.572 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
(...)
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 2, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: FileObserver: onEvent: event = 8, videoPath = media0.mp4
11-20 11:29:52.582 4987-5167/cc.closeup.android I/System.out﹕ **** CameraHost: write closed: videoPath = media0.mp4
FileObserver can be used to detect when a file has been finally written. It can be hooked into the getVideoDirectory() override in CameraHost:
FileObserver has the limitation that it only works accurately when stopRecording() is explicitly called. It is raised too late if the recording is terminated because a pre-defined files size or duration has been reached (see also https://github.com/commonsguy/cwac-camera/issues/242 ).
Using recorder.setOnInfoListener() does not work, because it may report finalization of a file before it has been actually written to disk.