I'm trying to create two camera previews in the same fragment layout, but I've encountered some strange issues, like:
the preview freezes and the camera preview isn't changed,
sometimes camera doesn't start at all.
Here is my custom SurfaceView for the Camera:
#SuppressWarnings("deprecation")
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
private List<Camera.Size> mSupportedPreviewSizes;
private Camera.Size mPreviewSize;
private int currentVolume;
private boolean isVolumeChanged = false;
public CameraPreview(Context context, Camera mCamera) {
super(context);
setCamera(mCamera);
init();
}
public CameraPreview(Context context) {
super(context);
}
public CameraPreview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Initialize the SurfaceView which will be used to display the camera preview.
* <p/>
* Only an general setup for the SurfaceView should be done in this method.
*/
private void init() {
setDrawingCacheEnabled(true);
}
/**
* Set the camera to the SurfaceView.
*
* #param camera
* The camera object which will be set to the SurfaceView.
*/
public void setCamera(Camera camera) {
mCamera = camera;
// supported preview sizes
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// empty. surfaceChanged will take care of stuff
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
LogUtils.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or reformatting changes here
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
// set the orientation of the pictures taken
//TODO: match this orientation value with the one used for display
parameters.setRotation(90);
// set the focus mode
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
} catch (RuntimeException e) {
// This could happen when concurrency of the camera is happening (especially in dual shot mode)
//TODO: optimize the camera flow to avoid this issue
LogUtils.d(TAG, "Error setting camera parameters: " + e.getMessage());
}
// Force the orientation in Portrait mode
// TODO: get the orientation from the device
mCamera.setDisplayOrientation(90);
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (IOException e) {
LogUtils.e(TAG, "Error starting camera preview: " + e.getMessage());
}
}
/**
* Disable the default shutter sound when taking a picture.
*/
private void disableShutterSound() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
boolean shutterState = mCamera.enableShutterSound(false);
LogUtils.i(TAG, "Shutter sound was" + (!shutterState ? "not " : " ") + "disabled");
} else {
LogUtils.i(TAG, "Trying to disable shutter sound by altering the system audio manager.");
// Backward compatibility method for disabling the shutter sound
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
currentVolume = audio.getStreamVolume(AudioManager.STREAM_SYSTEM);
audio.setStreamVolume(AudioManager.STREAM_SYSTEM, 0, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
MediaPlayer media = MediaPlayer.create(getContext(), R.raw.camera_shutter_click);
media.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
isVolumeChanged = true;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCamera != null) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
float ratio;
if (mPreviewSize.height >= mPreviewSize.width) {
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
} else {
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
}
// One of these methods should be used, second method squishes preview slightly
// setMeasuredDimension(width, width);
setMeasuredDimension(width, (int) (width * ratio));
// setMeasuredDimension((int) (width * ratio), height);
} else {
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null) {
return null;
}
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
continue;
}
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
/**
* Check if the Camera object was passed and set to the SurfaceView.
*
* #return {#code true} if the Camera object is valid, {#code false} otherwise
*/
public boolean isCameraSet() {
return null != mCamera;
}
public void takePicture() {
if (null != mCamera) {
// Disable the shutter sound when taking an picture
disableShutterSound();
mCamera.takePicture(shutterCallback, rawCallback, postViewCallback, jpegCallback);
}
}
private void resetCam() {
if (null != mCamera) {
mCamera.startPreview();
setCamera(mCamera);
}
}
/**
* The shutter callback occurs after the image is captured
*/
private Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
MediaPlayer.create(getContext(), R.raw.camera_shutter_click).start();
LogUtils.d(TAG, "onShutter");
}
};
/**
* The raw callback occurs when the raw image data is available.<br/>(<b>NOTE:</b> the data will be null if there is
* no raw image callback buffer available or the raw image callback buffer is not large enough to hold the raw
* image).
*/
private Camera.PictureCallback rawCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
LogUtils.d(TAG, "onPictureTaken - raw");
}
};
/**
* The jpeg callback occurs when the compressed image is available.
*/
private Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
if (isVolumeChanged) {
// Reset the audio settings which where set before trying to take an picture
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audio.setStreamVolume(AudioManager.STREAM_SYSTEM, currentVolume,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
// reset flag for volume control
isVolumeChanged = false;
}
new CameraSavePicture().execute(data);
resetCam();
LogUtils.d(TAG, "onPictureTaken - jpeg");
}
};
/**
* The postview callback occurs when a scaled, fully processed postview image is available. <br/> (<b>NOTE:</b> not
* all hardware supports this)
*/
private Camera.PictureCallback postViewCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
LogUtils.d(TAG, "onPictureTaken - postview");
}
};
}
This is the relevant part of the layout for the fragment:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<FrameLayout
android:id="#+id/full_camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<FrameLayout
android:id="#+id/small_camera_preview"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
/>
</RelativeLayout>
And this is the Camera fragment:
public class CameraFragment extends Fragment implements View.OnClickListener, EventBusInterface {
private static final String TAG = "Camera Fragment";
private ImageButton flipCameraButton;
private Button takePicture;
private RelativeLayout content;
private RelativeLayout controlPanel;
private CameraPreview fullCameraPreview;
private FrameLayout fullCameraLayout;
private CameraPreview smallCameraPreview;
private FrameLayout smallCameraLayout;
/**
* The mode of the camera which shoul initially be displayed.
*/
private CameraMode currentCameraMode = CameraMode.BACK;
/**
* Do not use to instantiate a fragment. Use newInstance method instead.
*/
public CameraFragment() {
}
/**
* Use this method to get a new instance of the fragment, and give eventually add parameters if you need to pass
* data to it and retrieve it in onCreate using getArguments.
*
* #return a new instance of the current fragment.
*/
public static CameraFragment newInstance() {
final CameraFragment cameraFragment = new CameraFragment();
Bundle arguments = new Bundle();
cameraFragment.setArguments(arguments);
return cameraFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the argument passed on the custom initialization of the fragment
Bundle arguments = getArguments();
if (null != savedInstanceState) {
// Get the last camera mode set by the user
CameraMode savedCameraMode = (CameraMode) savedInstanceState.getSerializable("CAMERA_MODE");
if (null != savedCameraMode) {
currentCameraMode = savedCameraMode;
}
}
// setRetainInstance(true); //Will ignore onDestroy Method (Nested Fragments no need this if parent
// have it)
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("CAMERA_MODE", currentCameraMode);
super.onSaveInstanceState(outState);
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_timeline_camera, container, false);
// BUG FIX: To avoid the window background overlapping this fragment we set it's background to transparent so
// that when the fragment is moved(scrolled) it will still be visible
this.getActivity().getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
initViews(view);
setListeners();
// Setup the camera mode which will initially be displayed.
setUpCameraMode(currentCameraMode);
return view;
}
#Override
public void onResume() {
super.onResume();
}
#SuppressWarnings("deprecation")
private void setUpCameraMode(final CameraMode cameraMode) {
// The front camera SurfaceView will need to be changed, release it first
releaseSmallCamera();
// The full SurfaceView has to be changed because the camera mode will be changed from Back to Front or
// Front to Back
releaseFullCamera();
resizeFullCamera(cameraMode == CameraMode.DUAL);
switch (cameraMode) {
case BACK:
setupFullCamera(CameraInfo.CAMERA_FACING_BACK);
break;
case FRONT:
setupFullCamera(CameraInfo.CAMERA_FACING_FRONT);
break;
case DUAL:
setUpDualCamera();
break;
}
}
#Override
public void onStart() {
super.onStart();
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
// This is required because we need to resume the camera when we get back in the fragment.
if (null != getView()) {
// Setup the camera mode which will initially be displayed.
setUpCameraMode(currentCameraMode);
}
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
// The front camera SurfaceView will need to be changed, release it first
releaseSmallCamera();
// The full SurfaceView has to be changed because the camera mode will be changed from Back to Front or
// Front to Back
releaseFullCamera();
}
/**
* Initialize the view for the current fragment.
*
* #param view
* The view which was inflated for this fragment.
*/
private void initViews(View view) {
controlPanel = (RelativeLayout) view.findViewById(R.id.control_panel);
fullCameraLayout = (FrameLayout) view.findViewById(R.id.full_camera_preview);
smallCameraLayout = (FrameLayout) view.findViewById(R.id.small_camera_preview);
flipCameraButton = (ImageButton) view.findViewById(R.id.flip_camera_button);
takePicture = (Button) view.findViewById(R.id.camera_take_picture);
}
/**
* Setup the listeners required for this fragment.
*/
private void setListeners() {
flipCameraButton.setOnClickListener(this);
takePicture.setOnClickListener(this);
}
/**
* Sets up the SurfaceView where the Full preview will be display. <br/> The full SurfaceView can show the camera
* that faces the same direction as the screen and the camera tjat faces the opposite direction as the screen.
*
* #param facingOfTheCamera
* The facing of the camera that has to be shown in the Full SurfaceView. <br/>Usually {#link
* CameraInfo#CAMERA_FACING_BACK} or {#link CameraInfo#CAMERA_FACING_FRONT}.<br/> If an invalid value is
* set, the preview for the back camera will be setup.
*/
#SuppressWarnings("deprecation")
private void setupFullCamera(int facingOfTheCamera) {
// Full camera can show the back & front cameras
CameraMode cameraMode;
if (facingOfTheCamera == CameraInfo.CAMERA_FACING_BACK) {
cameraMode = CameraMode.BACK;
} else if (facingOfTheCamera == CameraInfo.CAMERA_FACING_FRONT) {
cameraMode = CameraMode.FRONT;
} else {
// Default to the back camera
cameraMode = CameraMode.BACK;
}
if (null == fullCameraPreview) {
fullCameraPreview = new CameraPreview(getActivity(), CameraUtils.getCamera(cameraMode));
}
if (fullCameraLayout.getVisibility() != View.VISIBLE) {
fullCameraLayout.setVisibility(View.VISIBLE);
}
if (fullCameraLayout.getChildCount() == 0) {
// The View for displaying the Camera preview was not added in the layout, we need to add it now.
fullCameraLayout.addView(fullCameraPreview);
}
}
/**
* Sets up the SurfaceView where the Small preview will be display. <br/> The small SurfaceView will always show the
* camera that faces the same direction as the screen.
*/
private void setupSmallCamera() {
// Small camera always shows the camera that faces the same as the screen
if (null == smallCameraPreview) {
smallCameraPreview = new CameraPreview(getActivity(), CameraUtils.getCamera(CameraMode.FRONT));
}
smallCameraPreview.setZOrderOnTop(true);
// smallCameraPreview.setZOrderMediaOverlay(true);
if (smallCameraLayout.getVisibility() != View.VISIBLE) {
smallCameraLayout.setVisibility(View.VISIBLE);
}
if (smallCameraLayout.getChildCount() == 0) {
// The View for displaying the Camera preview was not added in the layout, we need to add it now.
smallCameraLayout.addView(smallCameraPreview);
}
}
/**
* Setup Full SurfaceView preview (display Back camera) and Small SurfaceView preview (display Front camera).
*/
#SuppressWarnings("deprecation")
private void setUpDualCamera() {
// In dual mode camera preview display, the full preview is showing the back camera & the small preview
// displays the front camera
setupFullCamera(CameraInfo.CAMERA_FACING_BACK);
setupSmallCamera();
}
private void resizeFullCamera(boolean shrink) {
// The dual camera doesn't work if two SurfaceViews overlap - use this to resize the full camera so that it
// is not overlapped by the small camera
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
if (shrink) {
p.addRule(RelativeLayout.BELOW, R.id.small_camera_preview);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
p.removeRule(RelativeLayout.BELOW);
} else {
p.addRule(RelativeLayout.BELOW, 0);
}
}
fullCameraLayout.setLayoutParams(p);
}
/**
* Release the Camera Object used for the full camera mode. <br/> Remove the SurfaceView from the layout and
* invalidate it.
*/
private void releaseFullCamera() {
if (null != fullCameraPreview) {
fullCameraPreview.releaseCamera();
}
if (null != fullCameraLayout) {
// Remove the SurfaceView from the layout
fullCameraLayout.removeAllViews();
fullCameraLayout.setVisibility(View.INVISIBLE);
}
// Invalidate the SurfaceView
fullCameraPreview = null;
}
/**
* Release the Camera Object used for the small camera mode. <br/> Remove the SurfaceView from the layout and
* invalidate it.
*/
private void releaseSmallCamera() {
if (null != smallCameraPreview) {
smallCameraPreview.releaseCamera();
}
if (null != smallCameraLayout) {
// Remove the SurfaceView from the layout
smallCameraLayout.removeAllViews();
smallCameraLayout.setVisibility(View.INVISIBLE);
}
// Invalidate the SurfaceView
smallCameraPreview = null;
}
/**
* Flip between the camera modes supported. <br/> The flip is like an infinite carousel.
*/
private void flipCamera() {
// Set up the next mode for the camera preview/s display
setUpCameraMode(currentCameraMode.getNext());
// Update the current mode of the camera preview/s display
currentCameraMode = currentCameraMode.getNext();
}
#Override
public void onClick(View v) {
final int id = v.getId();
switch (id) {
case R.id.flip_camera_button:
// Flip camera to the next mode available.
flipCamera();
break;
case R.id.camera_take_picture:
takePicture();
break;
}
}
private void takePicture() {
switch (currentCameraMode) {
case BACK:
case FRONT:
if (null != fullCameraPreview) {
fullCameraPreview.takePicture();
} else {
ToastModel toastModel = new ToastModel(ToastType.GENERAL, "Please wait for camera warming up!");
new CustomToast(getActivity()).displayToast(toastModel);
}
break;
case DUAL:
ToastModel toastModel = new ToastModel(ToastType.GENERAL, "Dual picture not supported yet!");
new CustomToast(getActivity()).displayToast(toastModel);
break;
}
}
#Override
public void onEvent(Object event) {
}
#Override
public void onEventMainThread(Object event) {
if (event instanceof String) {
ToastModel toastModel = new ToastModel(ToastType.GENERAL, (String) event);
new CustomToast(getActivity()).displayToast(toastModel);
}
}
#Override
public void onEventBackgroundThread(Object event) {
}
#Override
public void onEventAsync(Object event) {
}
}
What could be the issue for this weird behavior?
Currently I'm not trying to show both previews at the same time, but one by one.
LE:
I have updated the logic for how the flip between camera modes is done.
When only the front or back camera are displayed in the full SurfaceView everything works fine. The problem appears when I use both of them (not necessarily both Camera objects at the same time, but both SurfaceViews visible at the same time & simply change from one to another).
I noticed the following errors in the logs:
D/Camera﹕ [seungmin]_open start
D/Camera﹕ [China_security]_before
D/Camera﹕ [China_security]_after
D/Camera﹕ [seungmin]_open End
D/CameraPreview﹕ Error setting camera parameters: getParameters failed (empty parameters)
W/CameraBase﹕ mediaserver's remote binder Camera object died
W/CameraBase﹕ Camera service died!
W/CameraBase﹕ mediaserver's remote binder Camera object died
E/Camera﹕ Error 100
E/Camera﹕ Error 100
LE2:
I have updated my code & optimized the flow.
I have also figured out why the Camera dies - it was because the SurfaceView for the second Camera was placed on top of the first one. Once the SurfaceViews didn't overlap anymore everything started working fine.
My question now is how can I fix this issue? Why is this happening?
PS: Most of the tests where made on LG G2 device which supports dual shot directly.
You may have stumbled upon an Android limitation regarding overlapping SurfaceViews. You can read more about this limitation here.
Don't think of SurfaceViews as classic views, they function more like a placeholder for getting the screen area (position and size) you want to surrender control to a rendering component. Rendering is actually done on a separate window.
As a solution, you can try calling setZOrderOnTop(true) on the SurfaceView you want on top.This is false by default, so the 2 SurfaceViews will not be on the same z anymore - making them work; one drawback is that the one on top will also be on top of any view you have.
If they do end up working, this is not guaranteed to happen on all devices, so I recommend extensive testing on what devices you consider important.
Another thing you can research is to see if you can tap into the Camera feed and see if you can merge the 2 streams and feeding the result to 1 SurfaceView
Related
I am getting a very interesting behavior by Android Studio in my project. The calls to different activities are not executing sequentially!
I am building a project where in the calls are like this:
button.setOnClickListener()
{
(1) call to cusom camera acivity
(2) call to activity showing preview of latest captured image
(3) call to a doCrop() function
}
But when executed, the actual flow is:
(1) call to the doCrop() function
(2) call to activity showing preview of image captured
(3) call to cusom camera acivity
The custom camera activity handles all the necessary SurfaceHolder and SurfaceView operations.
Is this effect happening as the SurfaceView layout creation and destroying takes more time and android switches to easier one task first?
Even so, it should skip to preview activity and not to doCrop() call.
What is happening here? Please give some pointers !
Thank you!
EDIT:
The naming is:
MainActivity - main activity
Preview - creates camera instance
CameraPreview - Handles SurfaceView etc
ImagePreview -Shows specified image
Main activity code:
photo.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
File temp = new File(//path to store image);
imageUri=Uri.fromFile(temp);
Intent intent = new Intent(MainActivity.this,Preview.class);
startActivity(intent);
// Image Preview Activity
Intent intent1=new Intent(MainActivity.this,ImagePreview.class);
startActivity(intent1);
//Crop function
doCrop();
}
});
The preview activity code:
public class Preview extends Activity {
private static final String TAG = "CamTestActivity";
CameraPreview preview;
Button buttonClick;
Camera camera;
Activity act;
Context ctx;
Uri uri;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ctx = this;
act = this;
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_preview);
preview = new CameraPreview(this, (SurfaceView)findViewById(R.id.surfaceView));
preview.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
((FrameLayout) findViewById(R.id.layout)).addView(preview);
preview.setKeepScreenOn(true);
buttonClick = (Button) findViewById(R.id.btnCapture);
buttonClick.setOnClickListener(new OnClickListener()
{
public void onClick(View v) {
camera.takePicture(shutterCallback, rawCallback, pngCallback);
}
});
}
#Override
protected void onResume() {
super.onResume();
int numCams = Camera.getNumberOfCameras();
if(numCams > 0){
try{
camera = Camera.open(0);
camera.startPreview();
preview.setCamera(camera);
} catch (RuntimeException ex){
Toast.makeText(ctx, getString(R.string.camera_not_found), Toast.LENGTH_LONG).show();
}
}
}
#Override
protected void onPause() {
if(camera != null) {
camera.stopPreview();
preview.setCamera(null);
camera.release();
camera = null;
}
super.onPause();
}
private void resetCam() {
camera.startPreview();
preview.setCamera(camera);
}
private void refreshGallery(File file) {
Intent mediaScanIntent = new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(file));
sendBroadcast(mediaScanIntent);
}
ShutterCallback shutterCallback = new ShutterCallback() {
public void onShutter() {
}
};
PictureCallback rawCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
}
};
PictureCallback pngCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
new SaveImageTask().execute(data);
resetCam();
Log.d(TAG, "onPictureTaken - png");
}
};
private class SaveImageTask extends AsyncTask<byte[], Void, Void> {
#Override
protected Void doInBackground(byte[]... data) {
// save the image
}
catch (Exception e) { e.printStackTrace();}
return null;
}
}
}
CameraPreview code:
class CameraPreview extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "Preview";
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Size mPreviewSize;
List<Size> mSupportedPreviewSizes;
Camera mCamera;
CameraPreview(Context context, SurfaceView sv) {
super(context);
mSurfaceView = sv;
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
requestLayout();
// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height / previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width / previewWidth;
child.layout(0, (height - scaledChildHeight) / 2,
width, (height + scaledChildHeight) / 2);
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
mCamera.stopPreview();
}
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if(mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
}
}
}
I think this is more of a design issue.
Should you really be opening more than one activity at once?
When an activity is started, it's put on top of the history stack, which could give the illusion that it's happening the other way round.
But I can't comment any further until I see code.
UPDATE: Yep, design issue.
This is the way it should work:
Onclick action in MainActivity should ONLY open Preview activity using startActivityForResult().
User takes photo, triggering setResult() and closing the camera activity.
Result gets returned to MainActivity, triggering onActivityResult()
Code in onActivityResult starts ImagePreview activity.
onCreate function in ImagePreview triggers doCrop();
I do suggest you have a good read of the Android Documentation here:
http://developer.android.com/guide/components/activities.html
Android is less flexible (or more helpful) than other platforms and it's essential that you have a good understanding of the API.
Activity startup code is asynchronous, and if you launch several activities simultaneously, like this:
startActivity(intent1);
startActivity(intent2);
startActivity(intent3);
it is not guaranteed, that they'll be shown to user sequentially, that is, in "launch order".
This is expected behaviour, and you normally should not do such a thing, as it is indication of your application design flaw
So what you are trying to do is launch 3 activities simultaneously.These all need to be handled in onActivityResult(). But a user cannot access 3 activities simultaneously.
so modifying your flow.
it should be something like
start CameraActivity
In the onActivityResult() of your calling activity ,you call do crop and do what you want.
You need to revisit your workflow because:
you are not launching 3 activites.
you are triggerring events based on user interaction and hence the sequence of functions should not be controlled from your code.
Your tasks are executing in wrong order because they are called asynchronously. To execute them in the order you have specified make a synchronous call, which can be done using startActivityForResult(intent, requestCode);
I have created a Surfaceview to show my camera preview. I am setting surface view width and height at run time using onMeasure() function.
If i comment "setLayoutParams(lp)" from my onMeasure() function then my surface view is getting created and camera preview is show. But it is not proper as surface width and height is not able to hold the aspect ratio of camera preview. Due to that preview looks bit stretched. To avoid the stretched i am calculating aspect ratio and based on the aspect ratio i am setting width and height of surfaceview. So that it can show the correct camera preview.
For the very first time surfaceCreated function is not called. As i have implemented the camera switch function so when i switch the camera then surfaceCreated function is called and after that it works fine.
Could you please let me know how "setLayoutParams(lp)" is affecting Surfaceview creation?
Note : - I am adding camera preview object to FrameLayout. Which is part of my fragment layout.
Below is my CameraPreview class.
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
String TAG = getClass().getSimpleName();
private SurfaceHolder mHolder;
private Camera mCamera;
Double aspectRatio;
Context context;
public CameraPreview(Context context) {
super(context);
Log.d(TAG, " CameraPreview constructor");
this.context = context;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
setBackgroundDrawable(getResources().getDrawable(R.drawable.face_capture_layout_border));
}
/**
* Extract supported preview and flash modes from the camera.
*
* #param camera
*/
public void setCamera(Camera camera) {
mCamera = camera;
Camera.Parameters params = mCamera.getParameters();
Camera.Size pictureSize = getLargestPictureSize(params);
params.setPictureSize(pictureSize.width, pictureSize.height);
aspectRatio = new Double((float) pictureSize.width / pictureSize.height);
mCamera.setParameters(params);
requestLayout();
}
private Camera.Size getLargestPictureSize(Camera.Parameters params) {
Camera.Size bestSize = null;
List<Camera.Size> sizeList = params.getSupportedPictureSizes();
bestSize = sizeList.get(0);
for (int i = 1; i < sizeList.size(); i++) {
if ((sizeList.get(i).width * sizeList.get(i).height) >
(bestSize.width * bestSize.height)) {
bestSize = sizeList.get(i);
}
}
return bestSize;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, " Surface created");
Surface surface = getHolder().getSurface();
Log.d(TAG, "Surface is in created valid =>" + surface.isValid());
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
Log.d(TAG, " Surface destroyed");
// Surface will be destroyed when we return, so stop the preview.
}
void startCameraPreview() {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
}
catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.d(TAG, " Surface changed");
startCameraPreview();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "On measure");
//Parent frame size
View parent = (View) getParent();
int width = parent.getWidth();
int height = parent.getHeight();
int surfaceViewWidth = (int) (height / aspectRatio);
int surfaceViewHeight = (int) (height);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(surfaceViewWidth, surfaceViewHeight);
lp.gravity = Gravity.CENTER;
setLayoutParams(lp);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
I am writing a custom camera for my app. It's working fine when opening the activity with either of the back or front cameras. But I'm really struggling with switching cameras inside the activity.
When I tap the switching camera button, the preview freezes and nothing happens. I have tried the answers and tips suggested in other questions related to the camera and the preview, but nothing works.
Here is my activity:
public class CameraActivity extends Activity implements SurfaceHolder.Callback {
//================================================================================
// Properties
//================================================================================
// Tag
private final String TAG = "CameraActivity";
// Camera and CameraPreview objects
private Camera mCamera;
// SurfaceHolder object
private SurfaceHolder mSurfaceHolder;
// private CameraPreview mPreview;
// ImageButtons
private ImageButton captureButton;
private ImageButton camSwitchButton;
// Picture previewLayout
FrameLayout previewLayout;
SurfaceView preview;
// Current camera is facing FRONT or BACK
private int currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
// Picture taking callback
private Camera.PictureCallback mPictureCallback;
//================================================================================
// Methods
//================================================================================
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Activity with no notification bar
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Set content view
setContentView(R.layout.activity_camera);
// Initialize activity and previews
InitActivity();
InitPreview();
// Set onClicks
}
/**
* Initialize activity views
*/
private void InitActivity() {
// Create an instance of Camera
mCamera = GetCameraInstance(currentCameraId);
// Set the SurfaceView
preview = (SurfaceView) findViewById(R.id.camera_preview);
mSurfaceHolder = preview.getHolder();
mSurfaceHolder.addCallback(this);
}
/**
* Initialize the camera preview
*/
private void InitPreview() {
if (mCamera != null) {
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
} catch (IOException e) {
// log
}
mCamera.startPreview();
}
}
#Override
protected void onPause() {
super.onPause();
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release(); // release the camera for other applications
// mCamera = null;
}
}
#Override
protected void onResume() {
super.onResume();
if (mCamera == null) {
InitActivity();
InitPreview();
}
}
/**
* Get camera instance
* #return Camera instance (null if doesn't exist)
*/
private Camera GetCameraInstance(int camid) {
Camera c = null;
try {
c = Camera.open(camid); // attempt to get a Camera instance
Camera.Parameters campar = c.getParameters();
// Set auto-focus
if (getPackageManager().hasSystemFeature("android.hardware.camera.autofocus")) {
campar.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
c.setParameters(campar);
}
catch (Exception e){
// log
}
return c; // returns null if camera is unavailable
}
/**
* Switch the camera from front to back and vica verca
*/
private void SwitchCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
// mCamera = null;
}
//swap the id of the camera to be used
if (currentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
}
else currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
// Remove the view and re-add it
ViewGroup rootView = (ViewGroup) preview.getParent();
rootView.removeView(previewLayout);
mCamera = GetCameraInstance(currentCameraId);
InitActivity();
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Logger.Log(TAG, "SurfaceCreated");
InitPreview();
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
// Return if no surface found
if (mSurfaceHolder.getSurface() == null) {
return;
}
// Start preview with new settings
Camera.Parameters params = mCamera.getParameters();
boolean isPortrait = IsPortrait();
// Configure the parameters (so the image is showing correctly)
ConfigureCameraParameters(params, isPortrait);
// Start preview
InitPreview();
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
/**
* Configure the camer parameters
* #param cameraParams Camera parameters
* #param isPortrait Is the camera in portrait mode?
*/
protected void ConfigureCameraParameters(Camera.Parameters cameraParams, boolean isPortrait) {
int angle;
Display display = getWindowManager().getDefaultDisplay();
// Correct the orientation
switch (display.getRotation()) {
case Surface.ROTATION_0: // This is display orientation
angle = 90; // This is camera orientation
break;
case Surface.ROTATION_90:
angle = 0;
break;
case Surface.ROTATION_180:
angle = 270;
break;
case Surface.ROTATION_270:
angle = 180;
break;
default:
angle = 90;
break;
}
mCamera.setDisplayOrientation(angle);
mCamera.setParameters(cameraParams);
}
/**
* Check whether the camera is in portrait mode
* #return True|False
*/
private boolean IsPortrait() {
return (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT);
}
}
Permissions in manifest:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
I really appreciate it if someone could help.
You are pretty close, you dont need to destroy the surface view everytime you switch the camera, you only need to destroy the camera session and then make sure you re hook up the camera back to the surfaceview so it can be displayed. I was having some trouble with your front facing rear facing logic on a Razr Max so I just switched it to 1 and 0.
private void SwitchCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
//swap the id of the camera to be used
if (currentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
currentCameraId = 0;
} else {
currentCameraId = 1;
}
mCamera = GetCameraInstance(currentCameraId);
InitPreview();
}
I have a custom camera application and I want for any preview sizes to display it in full screen mode without stretching the camera preview image.
For this, I need to make the surfaceView larger than the screen in order to keep aspect ratio, so actually the user will see less than camera actually captures.
For some reason, I cannot make the SurfaceView larger than the screen size.
What I've tried so far:
resize camera preview in surfaceChanged method
resize camera preview in on onMeasure method
resize it in in onLayout method
adding FLAG_LAYOUT_NO_LIMITS to activity - info
adding android:clipChildren for the surface view - info
setting width in xml: android:layout_width="852px"
getWindow().setLayout(852, 1280); in activity
but without any success - the behaviour is the same each time: it appears ok for 1 second and after that it gets stretched.
Here is the code:
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final int CAMERA_ROTATE_ANGLE = 90;
private SurfaceHolder cameraHolder;
private Camera androidHardCamera;
private Context context;
public CameraPreview(Context context) {
super(context);
this.context = context;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
cameraHolder = getHolder();
cameraHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
cameraHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
this.androidHardCamera = camera;
if (androidHardCamera != null) {
requestLayout();
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
if (androidHardCamera != null) {
androidHardCamera.stopPreview();//this is needed for devices with API level < 14 (from doc.: Starting
// from API level 14, this method, aka setDisplayOrientation, can be called when preview is active.)
androidHardCamera.setDisplayOrientation(CAMERA_ROTATE_ANGLE);//force the preview Display Orientation
// to Portrait (rotate camera orientation/display to portrait)
//holder.setFixedSize(852, 1280);
androidHardCamera.setPreviewDisplay(holder);
androidHardCamera.startPreview();
}
} catch (IOException e) {
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (cameraHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
androidHardCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) this.getLayoutParams();
layoutParams.height = 1280;
layoutParams.width = 852;
this.setLayoutParams(layoutParams);
//cameraHolder.setFixedSize(852, 1280);
requestLayout();
// start preview with new settings
try {
androidHardCamera.setPreviewDisplay(cameraHolder);
androidHardCamera.startPreview();
} catch (Exception e) {
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
// #Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// // super.onMeasure(widthMeasureSpec, heightMeasureSpec); //To change body of overridden methods use File | Settings | File Templates.
// //super.onMeasure(852, 1280);
// setMeasuredDimension(852, 1280);
// }
}
public class MyActivity extends Activity{
private Camera camera;
private CameraPreview previewCamera;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
setContentView(R.layout.camera_screen);
previewCamera = new CameraPreview(this);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(previewCamera);
//getWindow().setLayout(852, 1280);
}
#Override
protected void onResume() {
// Create an instance of Camera
camera = getCameraInstance(1);
if (camera == null) {
Toast.makeText(this, "Camera in use!", Toast.LENGTH_LONG).show();
} else {
previewCamera.setCamera(camera);
camera.stopPreview();
Camera.Parameters p = camera.getParameters();
p.setPreviewSize(176, 144);
// p.setPreviewSize(480, 800);
camera.setParameters(p);
startPreviewCamera();
}
super.onResume();
}
#Override
protected void onPause() {
releaseCameraAndPreview();
super.onPause();
}
public Camera getCameraInstance(int cameraInstance) {
Camera c = null;
try {
c = Camera.open(cameraInstance);
} catch (Exception e) {
// Camera is not available (in use or does not exist)
System.out.println("exception: " + e);
}
return c;
}
public void startPreviewCamera() {
//Force the preview Display Orientation to Portrait (rotate camera orientation/display to portrait)
camera.setDisplayOrientation(90);
camera.startPreview();
}
public void releaseCameraAndPreview() {
if (camera != null) {
camera.stopPreview(); // updating the preview surface
camera.setPreviewCallback(null);
// camera.lock(); //if we don't lock the camera, release() will fail on some devices
camera.release();
camera = null;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is the container for the camera preview screen -->
<FrameLayout android:id="#+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:layout_weight="1"/>
</LinearLayout>
Here is the entire project: https://www.dropbox.com/sh/96jih9kw5zmmnzy/z7VX16T30M
I am testing on a S3 device. On a S2 device seems to wok fine... I just do not know what to do more to solve this issue...
UPDATE 1
For example Sony Xperia has a screen display of 480 / 854.
One of the preview sizes I can use is 176 / 144.
In order to display full screen size I need to have the preview camera size of 698 / 854 - but I do not know how to set this value and where.
The code below is not working... the camera preview is stretched/elongated.
import android.app.Activity;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class CameraPreview extends Activity implements Preview.PreviewListener {
private Preview mPreview;
private Camera mCamera;
FrameLayout preview;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
// Create our Preview view and set it as the content of our activity.
mPreview = new Preview(this);
preview = (FrameLayout) findViewById(R.id.surface_camera);
preview.addView(mPreview);
Display display = getWindowManager().getDefaultDisplay();
getDisplaySize(display);
}
private static Point getDisplaySize(final Display display) {
final Point point = new Point();
try {
display.getSize(point);
} catch (java.lang.NoSuchMethodError ignore) {
point.x = display.getWidth();
point.y = display.getHeight();
}
System.out.println("============: Screen " + point.x + "/" + point.y);
return point;
}
#Override
protected void onResume() {
super.onResume();
mCamera = Camera.open(1);
mPreview.setCamera(mCamera, this);
}
#Override
protected void onPause() {
super.onPause();
if (mCamera != null) {
mPreview.setCamera(null, null);
mCamera.release();
mCamera = null;
}
}
#Override
public void onSurfaceChanged() {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) preview.getLayoutParams();
params.setMargins(0, -218, 0, 0);
preview.setLayoutParams(new FrameLayout.LayoutParams(480, 854));
preview.setLayoutParams(params);
preview.setVisibility(View.VISIBLE);
}
}
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.List;
class Preview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
private PreviewListener listener;
public static interface PreviewListener {
void onSurfaceChanged();
}
Preview(Context context) {
super(context);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera, PreviewListener listener) {
this.listener = listener;
mCamera = camera;
if (mCamera != null) {
List<Camera.Size> mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
for (Camera.Size s : mSupportedPreviewSizes) {
System.out.println("============: " + s.width + "/" + s.height);
}
requestLayout();
}
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e("Error: ", "IOException caused by setPreviewDisplay()", exception);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
mCamera.stopPreview();
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mCamera.stopPreview(); // pe Xpedia daca nu pui asta crapa la setDisplayOrientation
// Now that the size is known, set up the camera parameters and beginthe preview.
Camera.Parameters parameters = mCamera.getParameters();
mCamera.setDisplayOrientation(90);
parameters.setPreviewSize(176, 144);
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
}
// #Override
// protected void onSizeChanged(\int w, int h, int oldw, int oldh) {
// super.onSizeChanged(w, h, oldw, oldh);
// //setLayoutParams(new LayoutParams((int)RATIO * w, w));
//
// FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
// setLayoutParams(new FrameLayout.LayoutParams(960, 1280));
// params.setMargins(0, -120, 0,0);
// setLayoutParams(params);
//
// //preview.setVisibility(View.VISIBLE);
// }
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //To change body of overridden methods use File | Settings | File Templates.
// FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
// setLayoutParams(new FrameLayout.LayoutParams(698, 854));
// params.setMargins(0, -218, 0,0);
// setLayoutParams(params);
}
//https://stackoverflow.com/questions/11853297/change-size-of-android-custom-surfaceview
#Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
//setLayoutParams();
listener.onSurfaceChanged();
//(this).layout(0, 0, viewWidth , viewHeight);
}
}
}
This is a class test which calculates the correct surface view size based on display screen size and camera preview size:
public class Test {
/**
* Determine proper width to be used for surface view in order to not stretch the camera live preview.
*/
public static void main(String[] args) {
// camera preview size:
int surfaceViewWidth = 176;
int surfaceViewHeight = 144;
int holder;
if (surfaceViewWidth > surfaceViewHeight) {
holder = surfaceViewWidth;
surfaceViewWidth = surfaceViewHeight;
surfaceViewHeight = holder;
}
//device screen display sizes:
int width = 480;
int height = 854;
double sc1 = (double) width / surfaceViewWidth;
double sc2 = (double) height / surfaceViewHeight;
double rez;
if (sc1 > sc2) {
rez = sc1;
} else {
rez = sc2;
}
System.out.println("Width/height: " + (int) (surfaceViewWidth * rez) + "/" + (int) (surfaceViewHeight * rez)); // size of the preview size we need to set
System.out.println(((int) (surfaceViewWidth * rez))-width); // difference between preview size and device screen size = whit how much is bigger the preview size than screen size
}
}
First, remove source of crashes: startPreviewCamera called in onResume.
Camera preview shall be started in SurfaceHolder.Callback methods.
Then you should know that you can set preview size only to sizes reported by Camera.Parameters.getSupportedPreviewSizes. And these sizes will most likely be smaller or equal to device's screen size.
Then you simply call
Camera.Parameters p = camera.getParameters();
p.setPreviewSize(w, h); // one of supported sizes
camera.setParameters(p);
Then Surface of preview will have that size (possibly rotated and w/h swapped). And this surface will be rescaled by Android to size of your CameraPreview view when being drawn, so it's also important how you set size of your CameraPreview.
You can set fixed size of your CameraPreview simply by calling
previewCamera.setLayoutParams(new FrameLayout.LayoutParams(w, h));
So in short, you set requested preview size in Camera.setParameters, and you size your preview view as desired, possibly to same size as preview, as is your requirement. Your preview view then may be equal to screen size or be smaller (assuming camera doesn't provide preview bigger than screen). If camera provides bigger preview than screen, you can still call preview.setX, preview.setY to move it around.
I would like to effectively make a simple digital zoom for the camera preview, so I thought I would simply resize my SurfaceView to be larger than the screen. Other questions (such as 3813049) seem to indicate that this is easy, so I created the sample code below which I expect to let me see only half of the image horizontally (since the SurfaceView is twice as wide as the screen) and have the image only take up half of the screen horizontally. However, running it (when targeted to SDK version 4 on my Thunderbolt with Android 2.2.1) results in being able to see the whole image horizontally while filling the screen horizontally. The SurfaceView appears to behave as intended vertically (when I make it smaller than the screen), but Android won't allow me to make the SurfaceView larger than the screen.
How can I implement a digital zoom? (No, I cannot use Camera.Parameters.setZoom; not only is this not supported by Android 1.6, but different cameras support and implement this differently)
public class MagnifyTestActivity extends Activity implements SurfaceHolder.Callback {
private MagnificationView mPreview;
private SurfaceHolder mHolder;
private Camera mCamera = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreview = new MagnificationView(this);
setContentView(mPreview);
mHolder = mPreview.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public class MagnificationView extends SurfaceView {
public MagnificationView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth()*2;
int height = display.getHeight()/2;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mHolder.setFixedSize(w, h);
mCamera.startPreview();
}
}
UPDATE: Based on #Pulkit Sethi's response, it is possible to stretch/magnify the SurfaceView vertically, just not horizontally. To magnify the SurfaceView vertically, simply replace display.getHeight()/2 with display.getHeight()*2 above. Also observe that changing the width doesn't produce any horizontal magnification, either in my code or in Pulkit's.
//Activity class
public class CameraActivity extends Activity implements SurfaceListener {
private static final String TAG = "CameraActivity";
Camera mCamera;
CameraPreview mPreview;
private FrameLayout mCameraPreview;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_camera);
mCamera = getCameraInstance();
mPreview = new CameraPreview(this, mCamera);
mCameraPreview = (FrameLayout) findViewById(R.id.camera_preview);
mCameraPreview.addView(mPreview);
}
#Override
protected void onPause() {
super.onPause();
releaseCamera();
}
private Camera getCameraInstance() {
Camera camera = null;
try {
camera = Camera.open();
} catch (Exception e) {
}
return camera;
}
private void releaseCamera() {
if (null != mCamera) {
mCamera.release();
}
mCamera = null;
}
#Override
public void surfaceCreated() {
//Change these mate
int width = 1000;
int height = 1000;
// Set parent window params
getWindow().setLayout(width, height);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
width, height);
mCameraPreview.setLayoutParams(params);
mCameraPreview.requestLayout();
}
}
// Preview class
public class CameraPreview extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
Context mContext;
Camera mCamera;
SurfaceHolder mHolder;
public interface SurfaceListener{
public void surfaceCreated();
}
SurfaceListener listener;
public CameraPreview(Context context, Camera camera) {
super(context);
mContext = context;
listener = (SurfaceListener)mContext;
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
// Required prior 3.0 HC
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
Parameters params = mCamera.getParameters();
//Change parameters here
mCamera.setParameters(params);
mCamera.startPreview();
listener.surfaceCreated();
} catch (Exception e) {
// TODO: handle exception
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
Log.i(TAG, "Surface changed called");
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
mCamera.setDisplayOrientation(90);
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
//Layout file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/camera_preview"
android:layout_width="300dp"
android:layout_height="400dp"
android:layout_centerHorizontal="true"
android:paddingTop="10dp" >
</FrameLayout>
</RelativeLayout>
You can't make your surfaceView bigger than the screen. That being said there are ways around it.
I found you can adjust the size of the canvas in the SurfaceView, which will allow zooming.
public class DrawingThread extends Thread {
private MagnificationView mainPanel;
private SurfaceHolder surfaceHolder;
private boolean run;
public DrawingThread(SurfaceHolder surface, MagnificationView panel){
surfaceHolder = surface;
mainPanel = panel;
}
public SurfaceHolder getSurfaceHolder(){
return surfaceHolder;
}
public void setRunning (boolean run){
this.run = run;
}
public void run(){
Canvas c;
while (run){
c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder){
mainPanel.OnDraw(c);
}
} finally {
if (c != null){
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
In the MagnificationView class add a method:
public void OnDraw(Canvas canvas){
if (canvas!=null){
canvas.save();
canvas.scale(scaleX,scaleY);
canvas.restore();
}
}
DrawingThread would be a thread you start in in your Activity. Also in your MagnificationView class override the OnTouchEvent to handle your own pinch-zoom (which will modify scaleX & scaleY.
Hope This solves your issue
What you can do is to get the window and set its height:
getWindow().setLayout(1000, 1000);
This makes your window larger than the screen making your root view and consequently your surfaceview, probably contained inside a Framelayout larger than screen.
This worked for me let me know.
The above would work no matter what. What you would want to do is listen for onSurfaceCreated event for your surface view. Then after you have the started the camera view and you are able to calculate size of your widget holding the preview, you would want to change size of the container widget.
The concept is your container widget (probably FrameLayout) wants to grow larger than screen. The screen itself is restricted by the activity so first set size of your window,
then set size of your framelayout (it would always be shrunk to max size of windows, so set accordingly).
I do all this logic after my onSurfaceCreated is finished I have started the preview. I listen for this event in my activity by implementing a small interface, as my Camera preview is a separate class.
Working on all API level >= 8
Here's my TouchSurfaceView's onMeasure that performs zoom:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension((int) (width * scaleFactor), (int) (height * scaleFactor));
}
This properly zooms in and out depending on scaleFactor.
I haven't tested this with camera, but it works properly with MediaPlayer (behaving as VideoView).