Add support to record video to FileDescriptor. (#732)

pull/744/head
Sewar 5 years ago committed by Mattia Iavarone
parent e6ec1a15ac
commit 1a88cd09f4
  1. 63
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/VideoResultTest.java
  2. 44
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 24
      cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java
  4. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java
  5. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  6. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  7. 2
      docs/_docs/capturing-media.md

@ -16,6 +16,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -27,7 +28,7 @@ public class VideoResultTest extends BaseTest {
private VideoResult.Stub stub = new VideoResult.Stub(); private VideoResult.Stub stub = new VideoResult.Stub();
@Test @Test
public void testResult() { public void testResultWithFile() {
File file = Mockito.mock(File.class); File file = Mockito.mock(File.class);
int rotation = 90; int rotation = 90;
Size size = new Size(20, 120); Size size = new Size(20, 120);
@ -73,6 +74,66 @@ public class VideoResultTest extends BaseTest {
assertEquals(result.getAudioBitRate(), audioBitRate); assertEquals(result.getAudioBitRate(), audioBitRate);
assertEquals(result.getAudio(), audio); assertEquals(result.getAudio(), audio);
assertEquals(result.getFacing(), facing); assertEquals(result.getFacing(), facing);
}
@Test
public void testResultWithFileDescriptor() {
FileDescriptor fileDescriptor = FileDescriptor.in;
int rotation = 90;
Size size = new Size(20, 120);
VideoCodec codec = VideoCodec.H_263;
Location location = Mockito.mock(Location.class);
boolean isSnapshot = true;
int maxDuration = 1234;
long maxFileSize = 500000;
int reason = VideoResult.REASON_MAX_DURATION_REACHED;
int videoFrameRate = 30;
int videoBitRate = 300000;
int audioBitRate = 30000;
Audio audio = Audio.ON;
Facing facing = Facing.FRONT;
stub.fileDescriptor = fileDescriptor;
stub.rotation = rotation;
stub.size = size;
stub.videoCodec = codec;
stub.location = location;
stub.isSnapshot = isSnapshot;
stub.maxDuration = maxDuration;
stub.maxSize = maxFileSize;
stub.endReason = reason;
stub.videoFrameRate = videoFrameRate;
stub.videoBitRate = videoBitRate;
stub.audioBitRate = audioBitRate;
stub.audio = audio;
stub.facing = facing;
VideoResult result = new VideoResult(stub);
assertEquals(result.getFileDescriptor(), fileDescriptor);
assertEquals(result.getRotation(), rotation);
assertEquals(result.getSize(), size);
assertEquals(result.getVideoCodec(), codec);
assertEquals(result.getLocation(), location);
assertEquals(result.isSnapshot(), isSnapshot);
assertEquals(result.getMaxSize(), maxFileSize);
assertEquals(result.getMaxDuration(), maxDuration);
assertEquals(result.getTerminationReason(), reason);
assertEquals(result.getVideoFrameRate(), videoFrameRate);
assertEquals(result.getVideoBitRate(), videoBitRate);
assertEquals(result.getAudioBitRate(), audioBitRate);
assertEquals(result.getAudio(), audio);
assertEquals(result.getFacing(), facing);
}
@Test(expected = RuntimeException.class)
public void testResultWithNoFile() {
VideoResult result = new VideoResult(stub);
result.getFile();
}
@Test(expected = RuntimeException.class)
public void testResultWithNoFileDescriptor() {
VideoResult result = new VideoResult(stub);
result.getFileDescriptor();
} }
} }

@ -86,6 +86,7 @@ import com.otaliastudios.cameraview.size.SizeSelectorParser;
import com.otaliastudios.cameraview.size.SizeSelectors; import com.otaliastudios.cameraview.size.SizeSelectors;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -1646,8 +1647,28 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param file a file where the video will be saved * @param file a file where the video will be saved
*/ */
public void takeVideo(@NonNull File file) { public void takeVideo(@NonNull File file) {
takeVideo(file, null);
}
/**
* Starts recording a video. Video will be written to the given file,
* so callers should ensure they have appropriate permissions to write to the file.
*
* @param fileDescriptor a file descriptor where the video will be saved
*/
public void takeVideo(@NonNull FileDescriptor fileDescriptor) {
takeVideo(null, fileDescriptor);
}
private void takeVideo(@Nullable File file, @Nullable FileDescriptor fileDescriptor) {
VideoResult.Stub stub = new VideoResult.Stub(); VideoResult.Stub stub = new VideoResult.Stub();
mCameraEngine.takeVideo(stub, file); if (file != null) {
mCameraEngine.takeVideo(stub, file, null);
} else if (fileDescriptor != null) {
mCameraEngine.takeVideo(stub, null, fileDescriptor);
} else {
throw new IllegalStateException("file and fileDescriptor are both null.");
}
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -1686,9 +1707,26 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* *
* @param file a file where the video will be saved * @param file a file where the video will be saved
* @param durationMillis recording max duration * @param durationMillis recording max duration
*
*/ */
public void takeVideo(@NonNull File file, int durationMillis) { public void takeVideo(@NonNull File file, int durationMillis) {
takeVideo(file, null, durationMillis);
}
/**
* Starts recording a video. Video will be written to the given file,
* so callers should ensure they have appropriate permissions to write to the file.
* Recording will be automatically stopped after the given duration, overriding
* temporarily any duration limit set by {@link #setVideoMaxDuration(int)}.
*
* @param fileDescriptor a file descriptor where the video will be saved
* @param durationMillis recording max duration
*/
public void takeVideo(@NonNull FileDescriptor fileDescriptor, int durationMillis) {
takeVideo(null, fileDescriptor, durationMillis);
}
private void takeVideo(@Nullable File file, @Nullable FileDescriptor fileDescriptor,
int durationMillis) {
final int old = getVideoMaxDuration(); final int old = getVideoMaxDuration();
addCameraListener(new CameraListener() { addCameraListener(new CameraListener() {
@Override @Override
@ -1707,7 +1745,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
}); });
setVideoMaxDuration(durationMillis); setVideoMaxDuration(durationMillis);
takeVideo(file); takeVideo(file, fileDescriptor);
} }
/** /**

@ -7,11 +7,12 @@ import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
import java.io.File;
import java.io.FileDescriptor;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.File;
/** /**
* Wraps the result of a video recording started by {@link CameraView#takeVideo(File)}. * Wraps the result of a video recording started by {@link CameraView#takeVideo(File)}.
*/ */
@ -30,6 +31,7 @@ public class VideoResult {
public int rotation; public int rotation;
public Size size; public Size size;
public File file; public File file;
public FileDescriptor fileDescriptor;
public Facing facing; public Facing facing;
public VideoCodec videoCodec; public VideoCodec videoCodec;
public Audio audio; public Audio audio;
@ -55,6 +57,7 @@ public class VideoResult {
private final int rotation; private final int rotation;
private final Size size; private final Size size;
private final File file; private final File file;
private final FileDescriptor fileDescriptor;
private final Facing facing; private final Facing facing;
private final VideoCodec videoCodec; private final VideoCodec videoCodec;
private final Audio audio; private final Audio audio;
@ -71,6 +74,7 @@ public class VideoResult {
rotation = builder.rotation; rotation = builder.rotation;
size = builder.size; size = builder.size;
file = builder.file; file = builder.file;
fileDescriptor = builder.fileDescriptor;
facing = builder.facing; facing = builder.facing;
videoCodec = builder.videoCodec; videoCodec = builder.videoCodec;
audio = builder.audio; audio = builder.audio;
@ -130,9 +134,25 @@ public class VideoResult {
*/ */
@NonNull @NonNull
public File getFile() { public File getFile() {
if (file == null) {
throw new RuntimeException("File is only available when takeVideo(File) is used.");
}
return file; return file;
} }
/**
* Returns the file descriptor where the video was saved.
*
* @return the File Descriptor of this video
*/
@NonNull
public FileDescriptor getFileDescriptor() {
if (fileDescriptor == null) {
throw new RuntimeException("FileDescriptor is only available when takeVideo(FileDescriptor) is used.");
}
return fileDescriptor;
}
/** /**
* Returns the facing value with which this video was recorded. * Returns the facing value with which this video was recorded.
* *

@ -38,6 +38,7 @@ import com.otaliastudios.cameraview.size.SizeSelectors;
import com.otaliastudios.cameraview.video.VideoRecorder; import com.otaliastudios.cameraview.video.VideoRecorder;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -549,7 +550,9 @@ public abstract class CameraBaseEngine extends CameraEngine {
} }
@Override @Override
public final void takeVideo(final @NonNull VideoResult.Stub stub, final @NonNull File file) { public final void takeVideo(final @NonNull VideoResult.Stub stub,
final @Nullable File file,
final @Nullable FileDescriptor fileDescriptor) {
getOrchestrator().scheduleStateful("take video", CameraState.BIND, new Runnable() { getOrchestrator().scheduleStateful("take video", CameraState.BIND, new Runnable() {
@Override @Override
public void run() { public void run() {
@ -558,7 +561,13 @@ public abstract class CameraBaseEngine extends CameraEngine {
if (mMode == Mode.PICTURE) { if (mMode == Mode.PICTURE) {
throw new IllegalStateException("Can't record video while in PICTURE mode"); throw new IllegalStateException("Can't record video while in PICTURE mode");
} }
if (file != null) {
stub.file = file; stub.file = file;
} else if (fileDescriptor != null) {
stub.fileDescriptor = fileDescriptor;
} else {
throw new IllegalStateException("file and fileDescriptor are both null.");
}
stub.isSnapshot = false; stub.isSnapshot = false;
stub.videoCodec = mVideoCodec; stub.videoCodec = mVideoCodec;
stub.location = mLocation; stub.location = mLocation;

@ -49,6 +49,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.io.File; import java.io.File;
import java.io.FileDescriptor;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -712,7 +713,9 @@ public abstract class CameraEngine implements
public abstract void takePictureSnapshot(final @NonNull PictureResult.Stub stub); public abstract void takePictureSnapshot(final @NonNull PictureResult.Stub stub);
public abstract boolean isTakingVideo(); public abstract boolean isTakingVideo();
public abstract void takeVideo(@NonNull VideoResult.Stub stub, @NonNull File file); public abstract void takeVideo(@NonNull VideoResult.Stub stub,
@Nullable File file,
@Nullable FileDescriptor fileDescriptor);
public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file); public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file);
public abstract void stopVideo(); public abstract void stopVideo();

@ -2,7 +2,6 @@ package com.otaliastudios.cameraview.video;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Handler;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
@ -220,7 +219,15 @@ public abstract class FullVideoRecorder extends VideoRecorder {
(float) stub.location.getLatitude(), (float) stub.location.getLatitude(),
(float) stub.location.getLongitude()); (float) stub.location.getLongitude());
} }
if (stub.file != null) {
mMediaRecorder.setOutputFile(stub.file.getAbsolutePath()); mMediaRecorder.setOutputFile(stub.file.getAbsolutePath());
} else if (stub.fileDescriptor != null) {
mMediaRecorder.setOutputFile(stub.fileDescriptor);
} else {
throw new IllegalStateException("file and fileDescriptor are both null.");
}
mMediaRecorder.setOrientationHint(stub.rotation); mMediaRecorder.setOrientationHint(stub.rotation);
// When using MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, the recorder might have stopped // When using MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, the recorder might have stopped
// before calling it. But this creates issues on Camera2 Legacy devices - they need a // before calling it. But this creates issues on Camera2 Legacy devices - they need a

@ -102,7 +102,9 @@ camera.addCameraListener(new CameraListener() {
|`isTakingPicture()`|Returns true if the camera is currently capturing a picture.| |`isTakingPicture()`|Returns true if the camera is currently capturing a picture.|
|`takePicture()`|Takes a high quality picture.| |`takePicture()`|Takes a high quality picture.|
|`takeVideo(File)`|Takes a high quality video.| |`takeVideo(File)`|Takes a high quality video.|
|`takeVideo(FileDescriptor)`|Takes a high quality video.|
|`takeVideo(File, long)`|Takes a high quality video, stopping after the given duration.| |`takeVideo(File, long)`|Takes a high quality video, stopping after the given duration.|
|`takeVideo(FileDescriptor, long)`|Takes a high quality video, stopping after the given duration.|
|`takePictureSnapshot()`|Takes a picture snapshot.| |`takePictureSnapshot()`|Takes a picture snapshot.|
|`takeVideoSnapshot(File)`|Takes a video snapshot.| |`takeVideoSnapshot(File)`|Takes a video snapshot.|
|`takeVideoSnapshot(File, long)`|Takes a video snapshot, stopping after the given duration.| |`takeVideoSnapshot(File, long)`|Takes a video snapshot, stopping after the given duration.|

Loading…
Cancel
Save