basic working video

pull/1/head
Dylan McIntyre 8 years ago
parent b448e09944
commit 52ca32f130
  1. 12
      README.md
  2. 2
      build.gradle
  3. 1
      camerakit/src/main/AndroidManifest.xml
  4. 390
      camerakit/src/main/java/com/flurgle/camerakit/Camera1.java
  5. 70
      camerakit/src/main/java/com/flurgle/camerakit/Camera2.java
  6. 51
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  7. 4
      camerakit/src/main/java/com/flurgle/camerakit/CameraViewImpl.java
  8. 2
      camerakit/src/main/java/com/flurgle/camerakit/PictureCaptureCallback.java
  9. 24
      camerakit/src/main/java/com/flurgle/camerakit/PreviewImpl.java
  10. 8
      camerakit/src/main/java/com/flurgle/camerakit/TextureViewPreview.java
  11. 82
      camerakit/src/main/java/com/flurgle/camerakit/encoding/Encoder.java
  12. 12
      camerakit/src/main/java/com/flurgle/camerakit/encoding/Frame.java
  13. 157
      camerakit/src/main/java/com/flurgle/camerakit/encoding/VideoEncoder.java
  14. 2
      camerakit/src/main/java/com/flurgle/camerakit/utils/AspectRatio.java
  15. 4
      camerakit/src/main/java/com/flurgle/camerakit/utils/DisplayOrientationDetector.java
  16. 2
      camerakit/src/main/java/com/flurgle/camerakit/utils/Size.java
  17. 28
      camerakit/src/main/java/com/flurgle/camerakit/utils/YuvUtils.java
  18. 2
      camerakit/src/main/res/values/attrs.xml
  19. 7
      demo/src/main/AndroidManifest.xml
  20. 19
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
  21. 31
      demo/src/main/java/com/flurgle/camerakit/demo/PreviewDialog.java
  22. 4
      demo/src/main/res/layout/activity_main.xml

@ -21,6 +21,7 @@ Try out all the unique features using the CameraKit Demo from the Google Play st
- [`ckFlash`](#ckflash)
- [`ckPictureMode`](#ckpicturemode)
- [`ckTapToFocus`](#cktaptofocus)
- [`ckAutoFocus`](#ckautofocus)
- [Capturing Images](#capturing-images)
- [Capturing Video](#capturing-video)
- [Automatic Permissions Behavior](#automatic-permissions-behavior)
@ -124,7 +125,8 @@ camera.setCameraListener(new CameraListener() {
camerakit:ckFacing="back"
camerakit:ckFlash="off"
camerakit:ckPictureMode="quality"
camerakit:ckTapToFocus="on" />
camerakit:ckTapToFocus="on"
camerakit:ckAutoFocus="true" />
```
#### `ckCropOutput`
@ -134,7 +136,6 @@ camera.setCameraListener(new CameraListener() {
| `true` | Crop the output image or video to only contain what can be seen on the `CameraView` preview. |
| `false` | Output the full image or video regardless of what is visible on the `CameraView` preview. |
#### `ckFacing`
| Value | Description |
@ -167,6 +168,13 @@ camera.setCameraListener(new CameraListener() {
| `invisible` | Tap to focus is enabled, but no focus circle appears. |
| `off` | Tap to focus is off. |
#### `ckAutoFocus`
| Value | Description |
| --------------| -------------|
| `true` | Continuously allow the `CameraView` preview to adjust focus automatically. |
| `false` | Never adjust focus during preview. |
### Capturing Images
To capture an image just call `CameraView.capturePicture()`. Make sure you setup a `CameraListener` to handle the image callback.

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0-beta1'
classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

@ -2,6 +2,7 @@
package="com.flurgle.camerakit">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature

@ -0,0 +1,390 @@
package com.flurgle.camerakit;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.support.v4.util.SparseArrayCompat;
import android.view.SurfaceHolder;
import com.flurgle.camerakit.utils.Size;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public class Camera1 extends CameraViewImpl {
private static final int INVALID_CAMERA_ID = -1;
private static final SparseArrayCompat<String> FLASH_MODES = new SparseArrayCompat<>();
static {
FLASH_MODES.put(CameraKit.Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF);
FLASH_MODES.put(CameraKit.Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON);
FLASH_MODES.put(CameraKit.Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO);
}
private int mCameraId;
Camera mCamera;
private Camera.Parameters mCameraParameters;
private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
private boolean mShowingPreview;
private boolean mAutoFocus;
private int mFacing;
private int mFlash;
private int mDisplayOrientation;
private SortedSet<Size> mPreviewSizes;
private SortedSet<Size> mCaptureSizes;
private MediaRecorder mMediaRecorder;
Camera1(CameraListener callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
if (mCamera != null) {
setUpPreview();
adjustCameraParameters();
}
}
});
mPreviewSizes = new TreeSet<>();
mCaptureSizes = new TreeSet<>();
}
@Override
void start() {
chooseCamera();
openCamera();
if (mPreview.isReady()) {
setUpPreview();
}
mShowingPreview = true;
mCamera.startPreview();
}
@Override
void stop() {
if (mCamera != null) {
mCamera.stopPreview();
}
mShowingPreview = false;
releaseCamera();
}
@Override
boolean isCameraOpened() {
return mCamera != null;
}
@Override
void setFacing(int facing) {
if (mFacing == facing) {
return;
}
mFacing = facing;
if (isCameraOpened()) {
stop();
start();
}
}
@Override
int getFacing() {
return mFacing;
}
@Override
void setFlash(int flash) {
if (flash == mFlash) {
return;
}
if (setFlashInternal(flash)) {
mCamera.setParameters(mCameraParameters);
}
}
@Override
int getFlash() {
return mFlash;
}
@Override
boolean getAutoFocus() {
if (!isCameraOpened()) {
return mAutoFocus;
}
String focusMode = mCameraParameters.getFocusMode();
return focusMode != null && focusMode.contains("continuous");
}
@Override
void capturePicture() {
if (!isCameraOpened()) {
throw new IllegalStateException("Camera is not ready. Call start() before takePicture().");
}
if (getAutoFocus()) {
mCamera.cancelAutoFocus();
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
takePictureInternal();
}
});
} else {
takePictureInternal();
}
}
void takePictureInternal() {
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
getCameraListener().onPictureTaken(data);
camera.startPreview();
}
});
}
@Override
void captureStill() {
}
@Override
void startVideo() {
try {
prepareMediaRecorder();
} catch (IOException e) {
if (mMediaRecorder != null) {
mMediaRecorder.release();
}
return;
}
}
void prepareMediaRecorder() throws IOException {
mCamera.unlock();
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mMediaRecorder.setOutputFile(new File(getView().getContext().getExternalFilesDir(null), "video.mp4").getAbsolutePath());
mMediaRecorder.prepare();
mMediaRecorder.start();
}
@Override
void endVideo() {
if (mMediaRecorder != null) {
mMediaRecorder.stop();
}
getCameraListener().onVideoTaken(new File(getView().getContext().getExternalFilesDir(null), "video.mp4"));
}
@Override
void setDisplayOrientation(int displayOrientation) {
if (mDisplayOrientation == displayOrientation) {
return;
}
mDisplayOrientation = displayOrientation;
if (isCameraOpened()) {
int cameraRotation = calcCameraRotation(displayOrientation);
mCameraParameters.setRotation(cameraRotation);
mCamera.setParameters(mCameraParameters);
final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
if (needsToStopPreview) {
mCamera.stopPreview();
}
mCamera.setDisplayOrientation(cameraRotation);
if (needsToStopPreview) {
mCamera.startPreview();
}
}
}
private int calcCameraRotation(int rotation) {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return (360 - (mCameraInfo.orientation + rotation) % 360) % 360;
} else { // back-facing
return (mCameraInfo.orientation - rotation + 360) % 360;
}
}
private void setUpPreview() {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
if (needsToStopPreview) {
mCamera.stopPreview();
}
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
if (needsToStopPreview) {
mCamera.startPreview();
}
} else {
mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void adjustCameraParameters() {
SortedSet<Size> sizes = mPreviewSizes;
Size previewSize = chooseOptimalSize(sizes);
final Camera.Size currentSize = mCameraParameters.getPictureSize();
if (currentSize.width != previewSize.getWidth() || currentSize.height != previewSize.getHeight()) {
final Size pictureSize = mCaptureSizes.last();
if (mShowingPreview) {
mCamera.stopPreview();
}
mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
mPreview.setTruePreviewSize(previewSize.getWidth(), previewSize.getHeight());
mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
setAutoFocusInternal(mAutoFocus);
setFlashInternal(mFlash);
mCamera.setParameters(mCameraParameters);
if (mShowingPreview) {
mCamera.startPreview();
}
}
}
private Size chooseOptimalSize(SortedSet<Size> sizes) {
if (!mPreview.isReady()) { // Not yet laid out
return sizes.last(); // Return the smallest size
}
int desiredWidth;
int desiredHeight;
final int surfaceWidth = mPreview.getWidth();
final int surfaceHeight = mPreview.getHeight();
if (mDisplayOrientation == 90 || mDisplayOrientation == 270) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) { // Iterate from small to large
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
private void chooseCamera() {
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == mFacing) {
mCameraId = i;
return;
}
}
mCameraId = INVALID_CAMERA_ID;
}
private void openCamera() {
if (mCamera != null) {
releaseCamera();
}
mCamera = Camera.open(mCameraId);
mCameraParameters = mCamera.getParameters();
// Supported preview sizes
mPreviewSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
// Supported picture sizes;
mCaptureSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
mCaptureSizes.add(new Size(size.width, size.height));
}
adjustCameraParameters();
mCamera.setDisplayOrientation(calcCameraRotation(mDisplayOrientation));
getCameraListener().onCameraOpened();
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
getCameraListener().onCameraClosed();
}
}
private boolean setAutoFocusInternal(boolean autoFocus) {
mAutoFocus = autoFocus;
if (isCameraOpened()) {
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
mCameraParameters.setFocusMode(modes.get(0));
}
return true;
} else {
return false;
}
}
private boolean setFlashInternal(int flash) {
if (isCameraOpened()) {
List<String> modes = mCameraParameters.getSupportedFlashModes();
String mode = FLASH_MODES.get(flash);
if (modes != null && modes.contains(mode)) {
mCameraParameters.setFlashMode(mode);
mFlash = flash;
return true;
}
String currentMode = FLASH_MODES.get(mFlash);
if (modes == null || !modes.contains(currentMode)) {
mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mFlash = CameraKit.Constants.FLASH_OFF;
return true;
}
return false;
} else {
mFlash = flash;
return false;
}
}
}

@ -3,6 +3,7 @@ package com.flurgle.camerakit;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@ -13,7 +14,6 @@ import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
@ -22,6 +22,12 @@ import android.util.SparseIntArray;
import android.view.Surface;
import android.view.View;
import com.flurgle.camerakit.encoding.VideoEncoder;
import com.flurgle.camerakit.utils.AspectRatio;
import com.flurgle.camerakit.utils.Size;
import com.flurgle.camerakit.utils.YuvUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
@ -45,7 +51,7 @@ public class Camera2 extends CameraViewImpl {
private CameraCaptureSession mCaptureSession;
private CaptureRequest.Builder mPreviewRequestBuilder;
private ImageReader mImageReader;
private MediaRecorder mMediaRecorder;
private VideoEncoder mVideoEncoder;
private Semaphore mCameraOpenCloseLock;
@ -61,6 +67,10 @@ public class Camera2 extends CameraViewImpl {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private boolean mCropOutput;
private boolean mIsRecording;
Camera2(Context context, CameraListener cameraListener, PreviewImpl preview) {
super(cameraListener, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
@ -163,6 +173,11 @@ public class Camera2 extends CameraViewImpl {
return mFlash;
}
@Override
boolean getAutoFocus() {
return true;
}
@Override
void capturePicture() {
if (mFacing == INTERNAL_FACINGS.get(CameraKit.Constants.FACING_BACK)) {
@ -184,11 +199,17 @@ public class Camera2 extends CameraViewImpl {
if (mCamera == null) {
return;
}
mIsRecording = true;
}
@Override
void endVideo() {
mIsRecording = false;
if (mVideoEncoder != null) {
mVideoEncoder.stopEncoder();
mVideoEncoder = null;
}
}
@Override
@ -214,6 +235,10 @@ public class Camera2 extends CameraViewImpl {
}
}
void setCropOutput(boolean cropOutput) {
this.mCropOutput = cropOutput;
}
private boolean chooseCameraIdByFacing() {
try {
int internalFacing = mFacing;
@ -373,7 +398,7 @@ public class Camera2 extends CameraViewImpl {
Size previewSize = getOptimalPreviewSize();
mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());
mPreview.setTruePreviewSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = mPreview.getSurface();
try {
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@ -412,6 +437,8 @@ public class Camera2 extends CameraViewImpl {
360) % 360);
// Stop preview and capture a still picture.
mCaptureSession.stopRepeating();
mOnImageAvailableListener.allowCallback();
mCaptureSession.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
@ -437,7 +464,6 @@ public class Camera2 extends CameraViewImpl {
}
}
private final CameraDevice.StateCallback mCameraDeviceCallback = new CameraDevice.StateCallback() {
@Override
@ -545,15 +571,41 @@ public class Camera2 extends CameraViewImpl {
return;
}
if (!mAllowOneCallback) {
if (!mAllowOneCallback && !mIsRecording) {
image.close();
return;
}
mAllowOneCallback = false;
Rect crop = null;
if (mCropOutput) {
}
if (mAllowOneCallback) {
mAllowOneCallback = false;
byte[] out = YuvUtils.createRGB(image, crop);
getCameraListener().onPictureTaken(out);
}
if (mIsRecording) {
if (mVideoEncoder == null) {
try {
mVideoEncoder = new VideoEncoder(getView().getContext(), mFacing, image.getWidth(), image.getHeight());
} catch (IOException e) {
}
}
if (mVideoEncoder != null) {
byte[] out = YuvUtils.getYUVData(image);
try {
mVideoEncoder.encode(out);
} catch (Exception e) {
}
}
}
byte[] out = YuvUtils.createRGB(image);
getCameraListener().onPictureTaken(out);
image.close();
}
};

@ -6,8 +6,6 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
@ -23,11 +21,22 @@ import android.util.AttributeSet;
import android.view.Display;
import android.widget.FrameLayout;
import com.flurgle.camerakit.utils.DisplayOrientationDetector;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import static com.flurgle.camerakit.CameraKit.Constants.*;
import static com.flurgle.camerakit.CameraKit.Constants.FACING_BACK;
import static com.flurgle.camerakit.CameraKit.Constants.FACING_FRONT;
import static com.flurgle.camerakit.CameraKit.Constants.FLASH_AUTO;
import static com.flurgle.camerakit.CameraKit.Constants.FLASH_OFF;
import static com.flurgle.camerakit.CameraKit.Constants.FLASH_ON;
import static com.flurgle.camerakit.CameraKit.Constants.PICTURE_MODE_QUALITY;
import static com.flurgle.camerakit.CameraKit.Constants.PICTURE_MODE_SPEED;
import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_INVISIBLE;
import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_OFF;
import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_VISIBLE;
public class CameraView extends FrameLayout {
@ -64,6 +73,7 @@ public class CameraView extends FrameLayout {
private boolean mCropOutput;
private int mTapToFocus;
private boolean mAutoFocus;
private boolean mAdjustViewBounds;
@ -108,6 +118,10 @@ public class CameraView extends FrameLayout {
mTapToFocus = a.getInteger(R.styleable.CameraView_ckTapToFocus, TAP_TO_FOCUS_VISIBLE);
}
if (attr == R.styleable.CameraView_ckAutoFocus) {
mAutoFocus = a.getBoolean(R.styleable.CameraView_ckAutoFocus, true);
}
if (attr == R.styleable.CameraView_android_adjustViewBounds) {
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
}
@ -116,7 +130,7 @@ public class CameraView extends FrameLayout {
}
final PreviewImpl preview = new TextureViewPreview(context, this);
mCameraImpl = new Camera2(context, mCameraListener, preview);
mCameraImpl = new Camera1(mCameraListener, preview);
setFacing(mFacing);
setFlash(mFlash);
@ -236,6 +250,10 @@ public class CameraView extends FrameLayout {
this.mTapToFocus = tapToFocus;
}
public void setAutoFocus(boolean autoFocus) {
this.mAutoFocus = autoFocus;
}
public void setCameraListener(CameraListener cameraListener) {
this.mCameraListener = new CameraListenerMiddleWare(cameraListener);
mCameraImpl.setCameraListener(mCameraListener);
@ -253,11 +271,17 @@ public class CameraView extends FrameLayout {
}
public void startRecordingVideo() {
mCameraImpl.startVideo();
}
public void stopRecordingVideo() {
mCameraImpl.endVideo();
postDelayed(new Runnable() {
@Override
public void run() {
mCameraListener.onVideoTaken(new File(getContext().getExternalFilesDir(null), "video.mp4"));
}
}, 1000);
}
private void requestCameraPermission() {
@ -351,24 +375,13 @@ public class CameraView extends FrameLayout {
@Override
public void onPictureTaken(byte[] picture) {
super.onPictureTaken(picture);
if (mCropOutput) {
Bitmap bitmap = BitmapFactory.decodeByteArray(picture, 0, picture.length);
int previewWidth = mCameraImpl.mPreview.getWidth();
int previewHeight = mCameraImpl.mPreview.getWidth();
AspectRatio viewRatio = AspectRatio.of(getWidth(), getHeight());
AspectRatio previewRatio = AspectRatio.of(previewWidth, previewHeight);
mCameraListener.onPictureTaken(picture);
} else {
mCameraListener.onPictureTaken(picture);
}
mCameraListener.onPictureTaken(picture);
}
@Override
public void onVideoTaken(File video) {
super.onVideoTaken(video);
mCameraListener.onCameraOpened();
mCameraListener.onVideoTaken(video);
}
}

@ -2,7 +2,7 @@ package com.flurgle.camerakit;
import android.view.View;
abstract class CameraViewImpl {
public abstract class CameraViewImpl {
protected CameraListener mCameraListener;
@ -31,6 +31,8 @@ abstract class CameraViewImpl {
abstract int getFlash();
abstract boolean getAutoFocus();
abstract void capturePicture();
abstract void captureStill();

@ -6,7 +6,7 @@ import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.support.annotation.NonNull;
abstract class PictureCaptureCallback extends CameraCaptureSession.CaptureCallback {
public abstract class PictureCaptureCallback extends CameraCaptureSession.CaptureCallback {
static final int STATE_PREVIEW = 0;
static final int STATE_LOCKING = 1;

@ -5,7 +5,9 @@ import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
abstract class PreviewImpl {
import com.flurgle.camerakit.utils.AspectRatio;
public abstract class PreviewImpl {
interface Callback {
void onSurfaceChanged();
@ -14,9 +16,11 @@ abstract class PreviewImpl {
private Callback mCallback;
private int mWidth;
private int mHeight;
private int mTrueWidth;
private int mTrueHeight;
void setCallback(Callback callback) {
mCallback = callback;
}
@ -43,10 +47,19 @@ abstract class PreviewImpl {
return null;
}
void setBufferSize(int width, int height) {
void setTruePreviewSize(int width, int height) {
this.mTrueWidth = width;
this.mTrueHeight = height;
AspectRatio aspectRatio = AspectRatio.of(width, height);
int targetHeight = (int) (getView().getWidth() * aspectRatio.toFloat());
float scaleY = (float) targetHeight / (float) getView().getHeight();
float scaleY;
if (getView().getHeight() > 0) {
scaleY = (float) targetHeight / (float) getView().getHeight();
} else {
scaleY = 1;
}
if (scaleY > 1) {
getView().setScaleY(scaleY);
} else {
@ -57,6 +70,9 @@ abstract class PreviewImpl {
void setSize(int width, int height) {
mWidth = width;
mHeight = height;
// Refresh true preview size to adjust scaling
setTruePreviewSize(mTrueWidth, mTrueHeight);
}
int getWidth() {

@ -90,9 +90,11 @@ public class TextureViewPreview extends PreviewImpl {
@TargetApi(15)
@Override
void setBufferSize(int width, int height) {
super.setBufferSize(width, height);
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
void setTruePreviewSize(int width, int height) {
super.setTruePreviewSize(width, height);
if (mTextureView.getSurfaceTexture() != null) {
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
}
}
void configureTransform() {

@ -0,0 +1,82 @@
package com.flurgle.camerakit.encoding;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public abstract class Encoder {
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<Frame>(100);
int width;
int height;
int cameraFacing;
long startMS = 0;
byte[] rotatedFrameData = null;
byte[] planeManagedData = null;
boolean encoderStarted = false;
public Encoder(int cameraFacing, int width, int height) {
this.cameraFacing = cameraFacing;
this.height = height;
this.width = width;
}
abstract public void encode(byte[] rawData);
public abstract void stopEncoder();
public boolean hasEncoderStarted() {
return encoderStarted;
}
public void setStartMS(long ms) {
this.startMS = ms;
}
public static void YV12toYUV420PackedSemiPlanar(final byte[] input, byte[] out, final int width, final int height) {
final int frameSize = width * height;
final int qFrameSize = frameSize / 4;
for (int i = 0; i < input.length; i++) {
if (i < frameSize)
out[i] = input[i];
if (i < (qFrameSize)) {
out[frameSize + i * 2] = input[frameSize + i + qFrameSize]; // Cb (U)
out[frameSize + i * 2 + 1] = input[frameSize + i]; // Cr (V)
}
}
}
static byte[] NV21toYUV420p(byte[] data, int width, int height) {
int len_target = (width * height * 3) / 2;
byte[] buf_target = new byte[len_target];
System.arraycopy(data, 0, buf_target, 0, width * height);
for (int i = 0; i < (width * height / 4); i++) {
buf_target[(width * height) + i] = data[(width * height) + 2 * i + 1];
buf_target[(width * height) + (width * height / 4) + i] = data[(width * height) + 2 * i];
}
return buf_target;
}
public void rotateYUV420Degree90(byte[] data, byte[] output, int imageWidth, int imageHeight) {
int i = 0;
for (int x = 0; x < imageWidth; x++) {
for (int y = imageHeight - 1; y >= 0; y--) {
output[i] = data[y * imageWidth + x];
i++;
}
}
i = imageWidth * imageHeight * 3 / 2 - 1;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
output[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i--;
output[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i--;
}
}
}
}

@ -0,0 +1,12 @@
package com.flurgle.camerakit.encoding;
public class Frame {
public int id;
public byte[] frameData;
public Frame(int id) {
this.id = id;
}
}

@ -0,0 +1,157 @@
package com.flurgle.camerakit.encoding;
import android.content.Context;
import android.graphics.ImageFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
public class VideoEncoder extends Encoder {
File mVideoFile;
FileOutputStream mFOS;
MediaCodec mMediaCodec;
ByteBuffer[] inputBuffers;
ByteBuffer[] outputBuffers;
MediaFormat mediaFormat = null;
public static int frameID = 0;
public VideoEncoder(Context context, int cameraFacing, int width, int height) throws IOException {
super(cameraFacing, width, height);
try {
mVideoFile = new File(context.getExternalFilesDir(null), "video.mp4");
mFOS = new FileOutputStream(mVideoFile, false);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
queue = new ArrayBlockingQueue<>(100);
mMediaCodec = MediaCodec.createEncoderByType("video/avc");
mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
try {
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
frameID = 0;
rotatedFrameData = new byte[width * height * (ImageFormat.getBitsPerPixel(ImageFormat.YV12)) / 8];
planeManagedData = new byte[width * height * (ImageFormat.getBitsPerPixel(ImageFormat.YV12)) / 8];
encoderStarted = true;
mMediaCodec.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopEncoder() {
encoderStarted = false;
if (mMediaCodec != null) {
mMediaCodec.stop();
mMediaCodec.release();
mMediaCodec = null;
}
if (mFOS != null) {
try {
mFOS.close();
} catch (IOException e) {
}
}
}
@Override
public void encode(byte[] rawData) {
inputBuffers = mMediaCodec.getInputBuffers();
outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
int size = inputBuffer.limit();
inputBuffer.put(rawData);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, size, (System.currentTimeMillis() - startMS) * 1000, 0);
} else {
return;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
do {
if (outputBufferIndex >= 0) {
Frame frame = new Frame(frameID);
ByteBuffer outBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
int dataLength = 0;
outBuffer.get(outData);
dataLength = outData.length - 2;
frame.frameData = new byte[dataLength];
// skipping 0x00 0x80 while copying
System.arraycopy(outData, 2, frame.frameData, 0, dataLength);
try {
if (bufferInfo.offset != 0) {
mFOS.write(outData, bufferInfo.offset, outData.length - bufferInfo.offset);
} else {
mFOS.write(outData, 0, outData.length);
}
mFOS.flush();
} catch (IOException e) {
Log.e("Encoding", e.toString());
}
try {
queue.put(frame);
} catch (InterruptedException e) {
Log.e("EncodeDecode", "interrupted while waiting");
e.printStackTrace();
} catch (NullPointerException e) {
Log.e("EncodeDecode", "frame is null");
e.printStackTrace();
} catch (IllegalArgumentException e) {
Log.e("EncodeDecode", "problem inserting in the queue");
e.printStackTrace();
}
Log.d("EncodeDecode", "H263 frame enqueued. queue size now: " + queue.size());
frameID++;
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = mMediaCodec.getOutputBuffers();
Log.e("EncodeDecode", "output buffer of encoder : info changed");
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.e("EncodeDecode", "output buffer of encoder : format changed");
} else {
Log.e("EncodeDecode", "unknown value of outputBufferIndex : " + outputBufferIndex);
}
} while (outputBufferIndex >= 0);
}
}

@ -1,4 +1,4 @@
package com.flurgle.camerakit;
package com.flurgle.camerakit.utils;
import android.os.Parcel;
import android.os.Parcelable;

@ -1,4 +1,4 @@
package com.flurgle.camerakit;
package com.flurgle.camerakit.utils;
import android.content.Context;
import android.util.SparseIntArray;
@ -6,7 +6,7 @@ import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
abstract class DisplayOrientationDetector {
public abstract class DisplayOrientationDetector {
private final OrientationEventListener mOrientationEventListener;

@ -1,4 +1,4 @@
package com.flurgle.camerakit;
package com.flurgle.camerakit.utils;
import android.support.annotation.NonNull;

@ -1,9 +1,10 @@
package com.flurgle.camerakit;
package com.flurgle.camerakit.utils;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.Image;
import android.support.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -11,7 +12,22 @@ import java.nio.ByteBuffer;
public class YuvUtils {
public static byte[] createRGB(Image image) {
public static byte[] createRGB(Image image, @Nullable Rect crop) {
byte[] data = getYUVData(image);
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
if (crop == null) {
crop = new Rect(0, 0, image.getWidth(), image.getHeight());
}
yuvImage.compressToJpeg(crop, 50, out);
return out.toByteArray();
}
public static byte[] getYUVData(Image image) {
ByteBuffer bufferY = image.getPlanes()[0].getBuffer();
byte[] y = new byte[bufferY.remaining()];
bufferY.get(y);
@ -32,13 +48,7 @@ public class YuvUtils {
} catch (IOException e) {
}
byte data[] = outputStream.toByteArray();
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
yuvImage.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 50, out);
return out.toByteArray();
return outputStream.toByteArray();
}
}

@ -28,6 +28,8 @@
<enum name="off" value="2" />
</attr>
<attr name="ckAutoFocus" format="boolean" />
<attr name="android:adjustViewBounds" />
</declare-styleable>

@ -9,13 +9,16 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

@ -18,6 +18,8 @@ import com.flurgle.camerakit.CameraKit;
import com.flurgle.camerakit.CameraListener;
import com.flurgle.camerakit.CameraView;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnCheckedChanged;
@ -110,7 +112,22 @@ public class MainActivity extends AppCompatActivity {
@OnClick(R.id.captureVideo)
void captureVideo() {
camera.startRecordingVideo();
camera.postDelayed(new Runnable() {
@Override
public void run() {
camera.stopRecordingVideo();
}
}, 3000);
camera.setCameraListener(new CameraListener() {
@Override
public void onVideoTaken(File video) {
super.onVideoTaken(video);
PreviewDialog previewDialog = new PreviewDialog(MainActivity.this, video);
previewDialog.show();
}
});
}
@OnClick(R.id.toggleCamera)
@ -163,7 +180,7 @@ public class MainActivity extends AppCompatActivity {
invalidateParameters();
}
@OnFocusChange({ R.id.width, R.id.height })
@OnFocusChange({R.id.width, R.id.height})
void inputFocusChanged(View view, boolean f) {
blockInvalidation = view.isFocused();
}

@ -3,9 +3,14 @@ package com.flurgle.camerakit.demo;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.View;
import android.widget.ImageView;
import android.widget.VideoView;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
@ -13,22 +18,44 @@ import butterknife.ButterKnife;
public class PreviewDialog extends Dialog {
@BindView(R.id.image)
ImageView image;
ImageView imageView;
@BindView(R.id.video)
VideoView videoView;
private Bitmap bitmap;
private File video;
public PreviewDialog(@NonNull Context context, Bitmap bitmap) {
super(context);
this.bitmap = bitmap;
}
public PreviewDialog(@NonNull Context context, File video) {
super(context);
this.video = video;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_preview);
ButterKnife.bind(this);
image.setImageBitmap(bitmap);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else if (video != null) {
imageView.setVisibility(View.GONE);
videoView.setVideoPath(video.getAbsolutePath());
videoView.setMediaController(null);
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setLooping(true);
}
});
videoView.start();
}
}
}

@ -16,12 +16,12 @@
<com.flurgle.camerakit.CameraView xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_height="160dp"
android:layout_gravity="center_horizontal"
app:ckCropOutput="true"
app:ckFacing="back"
app:ckFlash="off"
app:ckPictureMode="speed" />
app:ckPictureMode="quality" />
<LinearLayout
android:layout_width="match_parent"

Loading…
Cancel
Save