Change videoQuality dynamically, adapt capture size to video AspectRatio, full docs

pull/1/head
Mattia Iavarone 7 years ago
parent 61c7b68e1e
commit 67812ac1dc
  1. 61
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  2. 36
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  3. 144
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  4. 29
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
  5. 2
      demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java
  6. 79
      demo/src/main/res/layout/activity_main.xml

@ -48,13 +48,7 @@ class Camera1 extends CameraImpl {
private int mDeviceOrientation;
private int mSensorOffset;
@Facing private int mFacing;
@Flash private int mFlash;
@Focus private int mFocus;
@ZoomMode private int mZoom;
@VideoQuality private int mVideoQuality;
@WhiteBalance private int mWhiteBalance;
@SessionType private int mSessionType;
private double mLatitude;
private double mLongitude;
private boolean mFocusOnTap;
@ -136,8 +130,8 @@ class Camera1 extends CameraImpl {
);
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- not allowed during preview
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
mCamera.setParameters(params);
}
mCamera.startPreview();
@ -358,8 +352,27 @@ class Camera1 extends CameraImpl {
@Override
void setVideoQuality(int videoQuality) {
this.mVideoQuality = videoQuality;
// TODO: restore preview size if needed.
if (mIsCapturingVideo) {
throw new IllegalStateException("Can't change video quality while recording a video.");
}
mVideoQuality = videoQuality;
if (isCameraOpened() && mSessionType == CameraKit.Constants.SESSION_TYPE_VIDEO) {
// Change capture size to a size that fits the video aspect ratio.
Size oldSize = mCaptureSize;
mCaptureSize = computeCaptureSize();
if (!mCaptureSize.equals(oldSize)) {
// New video quality triggers a new aspect ratio.
// Go on and see if preview size should change also.
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight());
mCamera.setParameters(params);
}
onSurfaceChanged();
}
Log.e(TAG, "captureSize: "+mCaptureSize);
Log.e(TAG, "previewSize: "+mPreviewSize);
}
}
@Override
@ -520,16 +533,19 @@ class Camera1 extends CameraImpl {
} else {
// Choose according to developer choice in setVideoQuality.
// The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc.
// So its output is our output.
CamcorderProfile camcorderProfile = getCamcorderProfile(mVideoQuality);
return new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
// We want the picture size to be the max picture consistent with the video aspect ratio.
List<Size> captureSizes = sizesFromList(params.getSupportedPictureSizes());
CamcorderProfile profile = getCamcorderProfile(mVideoQuality);
AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight);
return matchSize(captureSizes, targetRatio, new Size(0, 0), true);
}
}
private Size computePreviewSize() {
Camera.Parameters params = mCamera.getParameters();
List<Size> previewSizes = sizesFromList(params.getSupportedPreviewSizes());
return computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize());
AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight());
return matchSize(previewSizes, targetRatio, mPreview.getSurfaceSize(), false);
}
@ -761,20 +777,19 @@ class Camera1 extends CameraImpl {
/**
* Policy here is to return a size that is big enough to fit the surface size,
* and possibly consistent with the capture size.
* and possibly consistent with the target aspect ratio.
* @param sizes list of possible sizes
* @param captureSize size representing desired aspect ratio
* @param surfaceSize size representing the current surface size. We'd like the returned value to be bigger.
* @param targetRatio aspect ratio
* @param biggerThan size representing the current surface size
* @return chosen size
*/
private static Size computePreviewSize(List<Size> sizes, Size captureSize, Size surfaceSize) {
private static Size matchSize(List<Size> sizes, AspectRatio targetRatio, Size biggerThan, boolean biggestPossible) {
if (sizes == null) return null;
List<Size> consistent = new ArrayList<>(5);
List<Size> bigEnoughAndConsistent = new ArrayList<>(5);
final AspectRatio targetRatio = AspectRatio.of(captureSize.getWidth(), captureSize.getHeight());
final Size targetSize = surfaceSize;
final Size targetSize = biggerThan;
for (Size size : sizes) {
AspectRatio ratio = AspectRatio.of(size.getWidth(), size.getHeight());
if (ratio.equals(targetRatio)) {
@ -785,10 +800,16 @@ class Camera1 extends CameraImpl {
}
}
if (biggestPossible) {
if (bigEnoughAndConsistent.size() > 0) return Collections.max(bigEnoughAndConsistent);
if (consistent.size() > 0) return Collections.max(consistent);
return Collections.max(sizes);
} else {
if (bigEnoughAndConsistent.size() > 0) return Collections.min(bigEnoughAndConsistent);
if (consistent.size() > 0) return Collections.max(consistent);
return Collections.max(sizes);
}
}
}

@ -11,6 +11,13 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback {
protected final CameraView.CameraCallbacks mCameraListener;
protected final PreviewImpl mPreview;
@Facing protected int mFacing;
@Flash protected int mFlash;
@Focus protected int mFocus;
@VideoQuality protected int mVideoQuality;
@WhiteBalance protected int mWhiteBalance;
@SessionType protected int mSessionType;
CameraImpl(CameraView.CameraCallbacks callback, PreviewImpl preview) {
mCameraListener = callback;
mPreview = preview;
@ -47,4 +54,33 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback {
@Nullable
abstract ExtraProperties getExtraProperties();
@Facing
final int getFacing() {
return mFacing;
}
@Flash
final int getFlash() {
return mFlash;
}
@Focus
final int getFocus() {
return mFocus;
}
@WhiteBalance
final int getWhiteBalance() {
return mWhiteBalance;
}
@VideoQuality
final int getVideoQuality() {
return mVideoQuality;
}
@SessionType
final int getSessionType() {
return mSessionType;
}
}

@ -83,15 +83,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
getWorkerHandler().post(runnable);
}
@Facing private int mFacing;
@Flash private int mFlash;
@Focus private int mFocus;
// @Method private int mMethod;
@ZoomMode private int mZoom;
// @Permissions private int mPermissions;
@SessionType private int mSessionType;
@VideoQuality private int mVideoQuality;
@WhiteBalance private int mWhiteBalance;
private int mJpegQuality;
private boolean mCropOutput;
private int mDisplayOffset;
@ -118,13 +110,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@SuppressWarnings("WrongConstant")
private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0);
mFacing = a.getInteger(R.styleable.CameraView_cameraFacing, CameraKit.Defaults.DEFAULT_FACING);
mFlash = a.getInteger(R.styleable.CameraView_cameraFlash, CameraKit.Defaults.DEFAULT_FLASH);
mFocus = a.getInteger(R.styleable.CameraView_cameraFocus, CameraKit.Defaults.DEFAULT_FOCUS);
mSessionType = a.getInteger(R.styleable.CameraView_cameraSessionType, CameraKit.Defaults.DEFAULT_SESSION_TYPE);
int facing = a.getInteger(R.styleable.CameraView_cameraFacing, CameraKit.Defaults.DEFAULT_FACING);
int flash = a.getInteger(R.styleable.CameraView_cameraFlash, CameraKit.Defaults.DEFAULT_FLASH);
int focus = a.getInteger(R.styleable.CameraView_cameraFocus, CameraKit.Defaults.DEFAULT_FOCUS);
int sessionType = a.getInteger(R.styleable.CameraView_cameraSessionType, CameraKit.Defaults.DEFAULT_SESSION_TYPE);
int whiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE);
int videoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY);
mZoom = a.getInteger(R.styleable.CameraView_cameraZoomMode, CameraKit.Defaults.DEFAULT_ZOOM);
mWhiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE);
mVideoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY);
mJpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, CameraKit.Defaults.DEFAULT_JPEG_QUALITY);
mCropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, CameraKit.Defaults.DEFAULT_CROP_OUTPUT);
a.recycle();
@ -136,13 +128,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
addView(mFocusMarkerLayout);
mIsStarted = false;
setFacing(mFacing);
setFlash(mFlash);
setFocus(mFocus);
setSessionType(mSessionType);
setFacing(facing);
setFlash(flash);
setFocus(focus);
setSessionType(sessionType);
setZoomMode(mZoom);
setVideoQuality(mVideoQuality);
setWhiteBalance(mWhiteBalance);
setVideoQuality(videoQuality);
setWhiteBalance(whiteBalance);
if (!isInEditMode()) {
mOrientationHelper = new OrientationHelper(context) {
@ -159,19 +151,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mPreviewImpl.onDeviceOrientation(deviceOrientation);
}
};
/* focusMarkerLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent motionEvent) {
int action = motionEvent.getAction();
if (action == MotionEvent.ACTION_UP && mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) {
focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY());
}
mPreviewImpl.getView().dispatchTouchEvent(motionEvent);
return true;
}
}); */
}
mLifecycle = null;
}
@ -384,13 +363,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
destroy();
}
/**
* Starts the camera preview, if not started already.
* This should be called onResume(), or when you are ready with permissions.
*/
public void start() {
if (mIsStarted || !isEnabled()) {
// Already started, do nothing.
return;
}
if (checkPermissions(mSessionType)) {
if (checkPermissions(getSessionType())) {
mIsStarted = true;
run(new Runnable() {
@Override
@ -455,6 +439,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
}
/**
* Stops the current preview, if any was started.
* This should be called onPause().
*/
public void stop() {
if (!mIsStarted) {
// Already stopped, do nothing.
@ -484,6 +473,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/**
* Set location coordinates to be found later in the jpeg EXIF header
*
* @param latitude current latitude
* @param longitude current longitude
*/
@ -491,22 +481,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCameraImpl.setLocation(latitude, longitude);
}
/**
* Sets desired white balance to current camera session.
*
* @see CameraKit.Constants#WHITE_BALANCE_AUTO
* @see CameraKit.Constants#WHITE_BALANCE_CLOUDY
* @see CameraKit.Constants#WHITE_BALANCE_DAYLIGHT
* @see CameraKit.Constants#WHITE_BALANCE_FLUORESCENT
* @see CameraKit.Constants#WHITE_BALANCE_INCANDESCENT
*
* @param whiteBalance desired white balance behavior.
*/
public void setWhiteBalance(@WhiteBalance int whiteBalance) {
mWhiteBalance = whiteBalance;
mCameraImpl.setWhiteBalance(whiteBalance);
}
/**
* Returns the current white balance behavior.
* @return white balance value.
*/
@WhiteBalance
public int getWhiteBalance() {
return mWhiteBalance;
return mCameraImpl.getWhiteBalance();
}
@ -519,7 +517,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param facing a facing value.
*/
public void setFacing(@Facing final int facing) {
this.mFacing = facing;
run(new Runnable() {
@Override
public void run() {
@ -535,7 +532,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Facing
public int getFacing() {
return mFacing;
return mCameraImpl.getFacing();
}
@ -547,7 +544,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Facing
public int toggleFacing() {
switch (mFacing) {
int facing = mCameraImpl.getFacing();
switch (facing) {
case FACING_BACK:
setFacing(FACING_FRONT);
break;
@ -557,7 +555,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
break;
}
return mFacing;
return mCameraImpl.getFacing();
}
@ -572,7 +570,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param flash desired flash mode.
*/
public void setFlash(@Flash int flash) {
this.mFlash = flash;
mCameraImpl.setFlash(flash);
}
@ -583,7 +580,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Flash
public int getFlash() {
return mFlash;
return mCameraImpl.getFlash();
}
@ -596,7 +593,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Flash
public int toggleFlash() {
switch (mFlash) {
int flash = mCameraImpl.getFlash();
switch (flash) {
case FLASH_OFF:
setFlash(FLASH_ON);
break;
@ -611,7 +609,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
break;
}
return mFlash;
return mCameraImpl.getFlash();
}
@ -626,7 +624,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* @param focus a Focus value.
*/
public void setFocus(@Focus int focus) {
mFocus = focus;
mFocusMarkerLayout.setEnabled(focus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER);
if (focus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) {
focus = CameraKit.Constants.FOCUS_TAP;
@ -641,7 +638,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Focus
public int getFocus() {
return mFocus;
return mCameraImpl.getFocus();
}
@ -674,14 +671,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
public void setSessionType(@SessionType int sessionType) {
if (sessionType == mSessionType || !mIsStarted) {
if (sessionType == getSessionType() || !mIsStarted) {
// Check did took place, or will happen on start().
mSessionType = sessionType;
mCameraImpl.setSessionType(sessionType);
} else if (checkPermissions(sessionType)) {
// Camera is running. CameraImpl setSessionType will do the trick.
mSessionType = sessionType;
mCameraImpl.setSessionType(sessionType);
} else {
@ -700,7 +695,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@SessionType
public int getSessionType() {
return mSessionType;
return mCameraImpl.getSessionType();
}
@ -728,27 +723,62 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
/**
* Sets video recording quality. This is not guaranteed to be supported by current device.
* If it's not, a lower quality will be chosen, until a supported one is found.
* If sessionType is video, this might trigger a camera restart and a change in preview size.
*
* @see CameraKit.Constants#VIDEO_QUALITY_LOWEST
* @see CameraKit.Constants#VIDEO_QUALITY_QVGA
* @see CameraKit.Constants#VIDEO_QUALITY_480P
* @see CameraKit.Constants#VIDEO_QUALITY_720P
* @see CameraKit.Constants#VIDEO_QUALITY_1080P
* @see CameraKit.Constants#VIDEO_QUALITY_2160P
* @see CameraKit.Constants#VIDEO_QUALITY_HIGHEST
*
* @param videoQuality requested video quality
*/
public void setVideoQuality(@VideoQuality int videoQuality) {
this.mVideoQuality = videoQuality;
mCameraImpl.setVideoQuality(mVideoQuality);
mCameraImpl.setVideoQuality(videoQuality);
}
/**
* Gets the current video quality.
* @return the current video quality
*/
@VideoQuality
public int getVideoQuality() {
return mCameraImpl.getVideoQuality();
}
/**
* Sets the JPEG compression quality for image outputs.
* @param jpegQuality a 0-100 integer.
*/
public void setJpegQuality(int jpegQuality) {
this.mJpegQuality = jpegQuality;
if (jpegQuality <= 0 || jpegQuality > 100) {
throw new IllegalArgumentException("JPEG quality should be > 0 and <= 0");
}
mJpegQuality = jpegQuality;
}
/**
* Whether we should crop the picture output to match CameraView aspect ratio.
* This is only relevant if CameraView dimensions were somehow constrained
* (e.g. by fixed value or MATCH_PARENT) and do not match internal aspect ratio.
*
* Please note that this requires additional computations after the picture is taken.
*
* @param cropOutput whether to crop
*/
public void setCropOutput(boolean cropOutput) {
this.mCropOutput = cropOutput;
}
/**
* Sets a {@link CameraListener} instance to be notified of all
* interesting events that will happen during the camera lifecycle.

@ -40,6 +40,10 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
@BindView(R.id.cropModeRadioGroup)
RadioGroup cropModeRadioGroup;
// Video Quality:
@BindView(R.id.videoQualityRadioGroup)
RadioGroup videoQualityRadioGroup;
// Width:
@BindView(R.id.screenWidth)
TextView screenWidth;
@ -86,6 +90,7 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
cropModeRadioGroup.setOnCheckedChangeListener(cropModeChangedListener);
widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener);
heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener);
videoQualityRadioGroup.setOnCheckedChangeListener(videoQualityChangedListener);
}
private void message(String content, boolean important) {
@ -116,9 +121,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
if (mCapturingPicture) return;
mCapturingPicture = true;
final long startTime = System.currentTimeMillis();
final boolean snapshot = camera.getSessionType() != CameraKit.Constants.SESSION_TYPE_PICTURE;
final Size nativeSize = snapshot ? camera.getSnapshotSize() : camera.getCaptureSize();
message(snapshot ? "Capturing snapshot..." : "Capturing picture...", false);
final Size nativeSize = camera.getCaptureSize();
message("Capturing picture...", false);
camera.removeCameraListener(mPictureListener);
mPictureListener = new CameraListener() {
@Override
@ -226,6 +230,25 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
}
};
RadioGroup.OnCheckedChangeListener videoQualityChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturingVideo) return;
int videoQuality = CameraKit.Constants.VIDEO_QUALITY_HIGHEST;
switch (checkedId) {
case R.id.videoQualityLowest: videoQuality = CameraKit.Constants.VIDEO_QUALITY_LOWEST; break;
case R.id.videoQualityQvga: videoQuality = CameraKit.Constants.VIDEO_QUALITY_QVGA; break;
case R.id.videoQuality480p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_480P; break;
case R.id.videoQuality720p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_720P; break;
case R.id.videoQuality1080p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_1080P; break;
case R.id.videoQuality2160p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_2160P; break;
case R.id.videoQualityHighest: videoQuality = CameraKit.Constants.VIDEO_QUALITY_HIGHEST; break;
}
camera.setVideoQuality(videoQuality);
message("Video quality changed!", false);
}
};
@OnClick(R.id.widthUpdate)
void widthUpdateClicked() {
if (mCapturingPicture) return;

@ -51,9 +51,9 @@ public class VideoPreviewActivity extends Activity {
float viewWidth = videoView.getWidth();
lp.height = (int) (viewWidth * (videoHeight / videoWidth));
videoView.setLayoutParams(lp);
playVideo();
}
});
playVideo();
}

@ -389,6 +389,85 @@
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="VIDEO QUALITY"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<HorizontalScrollView
android:layout_marginTop="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioGroup
android:id="@+id/videoQualityRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkedButton="@+id/videoQuality480p"
android:orientation="horizontal">
<RadioButton
android:id="@+id/videoQualityLowest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="low" />
<RadioButton
android:id="@+id/videoQualityQvga"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QVGA" />
<RadioButton
android:id="@+id/videoQuality480p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="480p" />
<RadioButton
android:id="@+id/videoQuality720p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="720p" />
<RadioButton
android:id="@+id/videoQuality1080p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1080p" />
<RadioButton
android:id="@+id/videoQuality2160p"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2160p" />
<RadioButton
android:id="@+id/videoQualityHighest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="high" />
</RadioGroup>
</HorizontalScrollView>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</LinearLayout>

Loading…
Cancel
Save