Add max size and duration constraints to videos

pull/360/head
Mattia Iavarone 6 years ago
parent 20691effb3
commit 4f95c3e7d5
  1. 21
      cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java
  2. 34
      cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoder.java
  3. 70
      cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java
  4. 4
      cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
  5. 11
      cameraview/src/main/gles/com/otaliastudios/cameraview/VideoMediaEncoder.java
  6. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  7. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java
  8. 43
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java
  9. 8
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java

@ -19,29 +19,33 @@ class AudioMediaEncoder extends MediaEncoder {
private static final String MIME_TYPE = "audio/mp4a-latm"; private static final String MIME_TYPE = "audio/mp4a-latm";
private static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices. private static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices.
private static final int BIT_RATE = 64000;
public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel
public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec
private final Object mLock = new Object(); private final Object mLock = new Object();
private boolean mRequestStop = false; private boolean mRequestStop = false;
private Config mConfig;
static class Config { static class Config {
Config() { } int bitRate;
Config(int bitRate) {
this.bitRate = bitRate;
}
} }
AudioMediaEncoder(@NonNull Config config) { AudioMediaEncoder(@NonNull Config config) {
mConfig = config;
} }
@EncoderThread @EncoderThread
@Override @Override
void prepare(MediaEncoderEngine.Controller controller) { void prepare(MediaEncoderEngine.Controller controller, long maxLengthMillis) {
super.prepare(controller); super.prepare(controller, maxLengthMillis);
final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1); final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1);
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO); audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, mConfig.bitRate);
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
try { try {
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
@ -125,4 +129,9 @@ class AudioMediaEncoder extends MediaEncoder {
} }
} }
} }
@Override
int getBitRate() {
return mConfig.bitRate;
}
} }

@ -2,7 +2,6 @@ package com.otaliastudios.cameraview;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Build; import android.os.Build;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.util.Log; import android.util.Log;
@ -20,6 +19,8 @@ abstract class MediaEncoder {
private MediaCodec.BufferInfo mBufferInfo; private MediaCodec.BufferInfo mBufferInfo;
private MediaEncoderEngine.Controller mController; private MediaEncoderEngine.Controller mController;
private int mTrackIndex; private int mTrackIndex;
private long mMaxLengthMillis;
private boolean mMaxLengthReached;
/** /**
* Called to prepare this encoder before starting. * Called to prepare this encoder before starting.
@ -31,9 +32,10 @@ abstract class MediaEncoder {
* @param controller the muxer controller * @param controller the muxer controller
*/ */
@EncoderThread @EncoderThread
void prepare(MediaEncoderEngine.Controller controller) { void prepare(MediaEncoderEngine.Controller controller, long maxLengthMillis) {
mController = controller; mController = controller;
mBufferInfo = new MediaCodec.BufferInfo(); mBufferInfo = new MediaCodec.BufferInfo();
mMaxLengthMillis = maxLengthMillis;
} }
/** /**
@ -149,28 +151,40 @@ abstract class MediaEncoder {
encodedData.position(mBufferInfo.offset); encodedData.position(mBufferInfo.offset);
encodedData.limit(mBufferInfo.offset + mBufferInfo.size); encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
mController.write(mTrackIndex, encodedData, mBufferInfo); mController.write(mTrackIndex, encodedData, mBufferInfo);
mPreviousTime = mBufferInfo.presentationTimeUs; mLastPresentationTime = mBufferInfo.presentationTimeUs;
if (mStartPresentationTime == 0) {
mStartPresentationTime = mLastPresentationTime;
}
} }
mMediaCodec.releaseOutputBuffer(encoderStatus, false); mMediaCodec.releaseOutputBuffer(encoderStatus, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if (!mMaxLengthReached) {
if (!endOfStream) { if (mLastPresentationTime / 1000 - mStartPresentationTime / 1000 > mMaxLengthMillis) {
Log.w("VideoMediaEncoder", "reached end of stream unexpectedly"); mMaxLengthReached = true;
// Log.e("MediaEncoder", this.getClass().getSimpleName() + " requested stop at " + (mLastPresentationTime * 1000 * 1000));
mController.requestStop();
break;
}
} }
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break; // out of while break; // out of while
} }
} }
} }
} }
private long mPreviousTime = 0; private long mStartPresentationTime = 0;
private long mLastPresentationTime = 0;
protected long getPresentationTime() { long getPresentationTime() {
long result = System.nanoTime() / 1000L; long result = System.nanoTime() / 1000L;
// presentationTimeUs should be monotonic // presentationTimeUs should be monotonic
// otherwise muxer fail to write // otherwise muxer fail to write
if (result < mPreviousTime) { if (result < mLastPresentationTime) {
result = (mPreviousTime - result) + result; result = (mLastPresentationTime - result) + result;
} }
return result; return result;
} }
abstract int getBitRate();
} }

@ -19,15 +19,25 @@ import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class MediaEncoderEngine { class MediaEncoderEngine {
final static int STOP_BY_USER = 0;
final static int STOP_BY_MAX_DURATION = 1;
final static int STOP_BY_MAX_SIZE = 2;
private WorkerHandler mWorker; private WorkerHandler mWorker;
private ArrayList<MediaEncoder> mEncoders; private ArrayList<MediaEncoder> mEncoders;
private MediaMuxer mMediaMuxer; private MediaMuxer mMediaMuxer;
private int mMediaMuxerStartCount; private int mMediaMuxerStartCount;
private boolean mMediaMuxerStarted; private boolean mMediaMuxerStarted;
private Controller mController; private Controller mController;
private Listener mListener;
private int mStopReason = STOP_BY_USER;
private int mPossibleStopReason;
private final Object mLock = new Object();
MediaEncoderEngine(@NonNull File file, @NonNull VideoMediaEncoder videoEncoder, @Nullable AudioMediaEncoder audioEncoder) { MediaEncoderEngine(@NonNull File file, @NonNull VideoMediaEncoder videoEncoder, @Nullable AudioMediaEncoder audioEncoder,
final int maxDuration, final long maxSize, @Nullable Listener listener) {
mWorker = WorkerHandler.get("EncoderEngine"); mWorker = WorkerHandler.get("EncoderEngine");
mListener = listener;
mController = new Controller(); mController = new Controller();
mEncoders = new ArrayList<>(); mEncoders = new ArrayList<>();
mEncoders.add(videoEncoder); mEncoders.add(videoEncoder);
@ -44,16 +54,38 @@ class MediaEncoderEngine {
mWorker.post(new Runnable() { mWorker.post(new Runnable() {
@Override @Override
public void run() { public void run() {
// Trying to convert the size constraints to duration constraints,
// because they are super easy to check.
int bitRate = 0;
for (MediaEncoder encoder : mEncoders) {
bitRate += encoder.getBitRate();
}
int bytePerSecond = bitRate / 8;
long sizeMaxDuration = (maxSize / bytePerSecond) * 1000L;
long finalMaxDuration = Long.MAX_VALUE;
if (maxSize > 0 && maxDuration > 0) {
mPossibleStopReason = sizeMaxDuration < maxDuration ? STOP_BY_MAX_SIZE : STOP_BY_MAX_DURATION;
finalMaxDuration = Math.min(sizeMaxDuration, maxDuration);
} else if (maxSize > 0) {
mPossibleStopReason = STOP_BY_MAX_SIZE;
finalMaxDuration = sizeMaxDuration;
} else if (maxDuration > 0) {
mPossibleStopReason = STOP_BY_MAX_DURATION;
finalMaxDuration = maxDuration;
}
Log.e("MediaEncoderEngine", "Computed a max duration of " + (finalMaxDuration / 1000F));
for (MediaEncoder encoder : mEncoders) { for (MediaEncoder encoder : mEncoders) {
encoder.prepare(mController); encoder.prepare(mController, finalMaxDuration);
} }
} }
}); });
} }
// Stuff here might be called from multiple threads.
class Controller { class Controller {
int start(MediaFormat format) { int start(MediaFormat format) {
synchronized (mLock) {
if (mMediaMuxerStarted) { if (mMediaMuxerStarted) {
throw new IllegalStateException("Trying to start but muxer started already"); throw new IllegalStateException("Trying to start but muxer started already");
} }
@ -65,11 +97,15 @@ class MediaEncoderEngine {
} }
return track; return track;
} }
}
boolean isStarted() { boolean isStarted() {
synchronized (mLock) {
return mMediaMuxerStarted; return mMediaMuxerStarted;
} }
}
// Synchronization does not seem needed here.
void write(int track, ByteBuffer encodedData, MediaCodec.BufferInfo info) { void write(int track, ByteBuffer encodedData, MediaCodec.BufferInfo info) {
if (!mMediaMuxerStarted) { if (!mMediaMuxerStarted) {
throw new IllegalStateException("Trying to write before muxer started"); throw new IllegalStateException("Trying to write before muxer started");
@ -77,6 +113,16 @@ class MediaEncoderEngine {
Log.e("MediaEncoderEngine", "Writing data." + track); Log.e("MediaEncoderEngine", "Writing data." + track);
mMediaMuxer.writeSampleData(track, encodedData, info); mMediaMuxer.writeSampleData(track, encodedData, info);
} }
void requestStop() {
synchronized (mLock) {
mMediaMuxerStartCount--;
if (mMediaMuxerStartCount == 0) {
mStopReason = mPossibleStopReason;
stop();
}
}
}
} }
void start() { void start() {
@ -101,7 +147,7 @@ class MediaEncoderEngine {
}); });
} }
void stop(final Runnable onStop) { void stop() {
mWorker.post(new Runnable() { mWorker.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -111,17 +157,31 @@ class MediaEncoderEngine {
for (MediaEncoder encoder : mEncoders) { for (MediaEncoder encoder : mEncoders) {
encoder.release(); encoder.release();
} }
Exception error = null;
if (mMediaMuxer != null) { if (mMediaMuxer != null) {
// stop() throws an exception if you haven't fed it any data. // stop() throws an exception if you haven't fed it any data.
// We can just swallow I think. // But also in other occasions. So this is a signal that something
// went wrong, and we propagate that to the listener.
try {
mMediaMuxer.stop(); mMediaMuxer.stop();
mMediaMuxer.release(); mMediaMuxer.release();
} catch (Exception e) {
error = e;
}
mMediaMuxer = null; mMediaMuxer = null;
} }
onStop.run(); if (mListener != null) mListener.onEncoderStop(mStopReason, error);
mStopReason = STOP_BY_USER;
mListener = null;
mMediaMuxerStartCount = 0; mMediaMuxerStartCount = 0;
mMediaMuxerStarted = false; mMediaMuxerStarted = false;
} }
}); });
} }
interface Listener {
@EncoderThread
void onEncoderStop(int stopReason, @Nullable Exception e);
}
} }

@ -50,8 +50,8 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
@EncoderThread @EncoderThread
@Override @Override
void prepare(MediaEncoderEngine.Controller controller) { void prepare(MediaEncoderEngine.Controller controller, long maxLengthMillis) {
super.prepare(controller); super.prepare(controller, maxLengthMillis);
mEglCore = new EglCore(mConfig.eglContext, EglCore.FLAG_RECORDABLE); mEglCore = new EglCore(mConfig.eglContext, EglCore.FLAG_RECORDABLE);
mWindow = new EglWindowSurface(mEglCore, mSurface, true); mWindow = new EglWindowSurface(mEglCore, mSurface, true);
mWindow.makeCurrent(); // drawing will happen on the InputWindowSurface, which mWindow.makeCurrent(); // drawing will happen on the InputWindowSurface, which

@ -50,8 +50,8 @@ abstract class VideoMediaEncoder<C extends VideoMediaEncoder.Config> extends Med
@EncoderThread @EncoderThread
@Override @Override
void prepare(MediaEncoderEngine.Controller controller) { void prepare(MediaEncoderEngine.Controller controller, long maxLengthMillis) {
super.prepare(controller); super.prepare(controller, maxLengthMillis);
MediaFormat format = MediaFormat.createVideoFormat(mConfig.mimeType, mConfig.width, mConfig.height); MediaFormat format = MediaFormat.createVideoFormat(mConfig.mimeType, mConfig.width, mConfig.height);
// Set some properties. Failing to specify some of these can cause the MediaCodec // Set some properties. Failing to specify some of these can cause the MediaCodec
@ -59,7 +59,7 @@ abstract class VideoMediaEncoder<C extends VideoMediaEncoder.Config> extends Med
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, mConfig.bitRate); format.setInteger(MediaFormat.KEY_BIT_RATE, mConfig.bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, mConfig.frameRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, mConfig.frameRate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
format.setInteger("rotation-degrees", mConfig.rotation); format.setInteger("rotation-degrees", mConfig.rotation);
// Create a MediaCodec encoder, and configure it with our format. Get a Surface // Create a MediaCodec encoder, and configure it with our format. Get a Surface
@ -88,9 +88,8 @@ abstract class VideoMediaEncoder<C extends VideoMediaEncoder.Config> extends Med
drain(true); drain(true);
} }
@EncoderThread
@Override @Override
void release() { int getBitRate() {
super.release(); return mConfig.bitRate;
} }
} }

@ -512,6 +512,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCameraCallbacks.dispatchOnPictureTaken(result); mCameraCallbacks.dispatchOnPictureTaken(result);
} else { } else {
// Something went wrong. // Something went wrong.
mCameraCallbacks.dispatchError(new CameraException(CameraException.REASON_PICTURE_FAILED));
LOG.e("onPictureResult", "result is null: something went wrong."); LOG.e("onPictureResult", "result is null: something went wrong.");
} }
} }
@ -603,6 +604,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCameraCallbacks.dispatchOnVideoTaken(result); mCameraCallbacks.dispatchOnVideoTaken(result);
} else { } else {
// Something went wrong, lock the camera again. // Something went wrong, lock the camera again.
mCameraCallbacks.dispatchError(new CameraException(CameraException.REASON_VIDEO_FAILED));
mCamera.lock(); mCamera.lock();
} }
} }

@ -30,6 +30,18 @@ public class CameraException extends RuntimeException {
*/ */
public static final int REASON_DISCONNECTED = 3; public static final int REASON_DISCONNECTED = 3;
/**
* Could not take a picture or a picture snapshot,
* for some not specified reason.
*/
public static final int REASON_PICTURE_FAILED = 4;
/**
* Could not take a video or a video snapshot,
* for some not specified reason.
*/
public static final int REASON_VIDEO_FAILED = 5;
private int reason = REASON_UNKNOWN; private int reason = REASON_UNKNOWN;
CameraException(Throwable cause) { CameraException(Throwable cause) {
@ -41,6 +53,11 @@ public class CameraException extends RuntimeException {
this.reason = reason; this.reason = reason;
} }
CameraException(int reason) {
super();
this.reason = reason;
}
public int getReason() { public int getReason() {
return reason; return reason;
} }

@ -4,13 +4,15 @@ import android.graphics.SurfaceTexture;
import android.opengl.EGL14; import android.opengl.EGL14;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
/** /**
* A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs. * A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback { class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.RendererFrameCallback,
MediaEncoderEngine.Listener {
private static final String TAG = SnapshotVideoRecorder.class.getSimpleName(); private static final String TAG = SnapshotVideoRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -36,14 +38,14 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
mDesiredState = STATE_RECORDING; mDesiredState = STATE_RECORDING;
// TODO respect maxSize by doing inspecting frameRate, bitRate and frame size? // TODO respect maxSize by doing inspecting frameRate, bitRate and frame size?
// TODO do this at the encoder level, not here with a handler. // TODO do this at the encoder level, not here with a handler.
if (mResult.maxDuration > 0) { /* if (mResult.maxDuration > 0) {
new Handler().postDelayed(new Runnable() { new Handler().postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
mDesiredState = STATE_NOT_RECORDING; mDesiredState = STATE_NOT_RECORDING;
} }
}, (long) mResult.maxDuration); }, (long) mResult.maxDuration);
} } */
} }
@Override @Override
@ -85,9 +87,10 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
TextureMediaEncoder videoEncoder = new TextureMediaEncoder(config); TextureMediaEncoder videoEncoder = new TextureMediaEncoder(config);
AudioMediaEncoder audioEncoder = null; AudioMediaEncoder audioEncoder = null;
if (mResult.audio == Audio.ON) { if (mResult.audio == Audio.ON) {
audioEncoder = new AudioMediaEncoder(new AudioMediaEncoder.Config()); audioEncoder = new AudioMediaEncoder(new AudioMediaEncoder.Config(64000));
} }
mEncoderEngine = new MediaEncoderEngine(mResult.file, videoEncoder, audioEncoder); mEncoderEngine = new MediaEncoderEngine(mResult.file, videoEncoder, audioEncoder,
mResult.maxDuration, mResult.maxSize, SnapshotVideoRecorder.this);
mEncoderEngine.start(); mEncoderEngine.start();
mResult.rotation = 0; // We will rotate the result instead. mResult.rotation = 0; // We will rotate the result instead.
mCurrentState = STATE_RECORDING; mCurrentState = STATE_RECORDING;
@ -102,13 +105,7 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
} }
if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) { if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) {
mEncoderEngine.stop(new Runnable() { mEncoderEngine.stop();
@Override
public void run() {
// We are in the encoder thread.
dispatchResult();
}
});
mEncoderEngine = null; mEncoderEngine = null;
mCurrentState = STATE_NOT_RECORDING; mCurrentState = STATE_NOT_RECORDING;
mPreview.removeRendererFrameCallback(SnapshotVideoRecorder.this); mPreview.removeRendererFrameCallback(SnapshotVideoRecorder.this);
@ -116,4 +113,26 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
} }
} }
@EncoderThread
@Override
public void onEncoderStop(int stopReason, @Nullable Exception e) {
// If something failed, undo the result, since this is the mechanism
// to notify Camera1 about this.
if (e != null) {
mResult = null;
} else {
if (stopReason == MediaEncoderEngine.STOP_BY_MAX_DURATION) {
mResult.endReason = VideoResult.REASON_MAX_DURATION_REACHED;
} else if (stopReason == MediaEncoderEngine.STOP_BY_MAX_SIZE) {
mResult.endReason = VideoResult.REASON_MAX_SIZE_REACHED;
}
}
mEncoderEngine = null;
if (mPreview != null) {
mPreview.removeRendererFrameCallback(SnapshotVideoRecorder.this);
mPreview = null;
}
dispatchResult();
}
} }

@ -13,6 +13,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraListener; import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraOptions;
@ -47,6 +48,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
public void onCameraOpened(CameraOptions options) { onOpened(); } public void onCameraOpened(CameraOptions options) { onOpened(); }
public void onPictureTaken(PictureResult result) { onPicture(result); } public void onPictureTaken(PictureResult result) { onPicture(result); }
public void onVideoTaken(VideoResult result) { onVideo(result.getFile()); } public void onVideoTaken(VideoResult result) { onVideo(result.getFile()); }
public void onCameraError(@NonNull CameraException exception) {
onError(exception);
}
}); });
findViewById(R.id.edit).setOnClickListener(this); findViewById(R.id.edit).setOnClickListener(this);
@ -88,6 +92,10 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
} }
} }
private void onError(@NonNull CameraException exception) {
message("Got CameraException #" + exception.getReason(), true);
}
private void onPicture(PictureResult result) { private void onPicture(PictureResult result) {
if (camera.isTakingVideo()) { if (camera.isTakingVideo()) {
message("Captured while taking video. Size=" + result.getSize(), false); message("Captured while taking video. Size=" + result.getSize(), false);

Loading…
Cancel
Save