I have been working on a piece of code to preview and capture photo inside my fragment. I have made this by following Airpair guides on camera.
Problem is: Android Camera preview is rotated by 90degree and therefore looks stretched as well in certain devices.
Note: I tried this on my Samsung Galaxy Alpha (4.9 inch screen) and it worked properly. However in Huawei P8 (5 inch screen) and nexus 5x (5.2 inch screen) it was showing 90 degree rotated stretched preview.
I have seen others posting similar issue on `SO', however those solutions have already been integrated to this code.
for instance overriding protected void onLayout(boolean changed, int left, int top, int right, int bottom) is used to fix the rotation.
I would appreciate if you could point out what I've been missing.
/**
* A simple {#link Fragment} subclass.
*/
public class KycCameraFragment extends Fragment {
public static final String TAG = "KycCameraFragment";
// Native camera.
private Camera mCamera;
// View to display the camera output.
private CameraPreview mPreview;
// Reference to the containing view.
private View mCameraView;
public KycCameraFragment() {
// Required empty public constructor
}
#Override
public void onStart() {
super.onStart();
// register the event to listen.
GlobalBus.getBus().register(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_kyc_camera, container, false);
// Create our Preview view and set it as the content of our activity.
boolean opened = safeCameraOpenInView(view);
if(opened == false){
Log.d("CameraGuide","Error, Camera failed to open");
return view;
}
// Trap the capture button.
Button captureButton = (Button) view.findViewById(R.id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
return view;
}
#Subscribe(sticky = true)
public void getMessage(Events.FragmentIdTypeMessage fragmentIdTypeMessage) {
TextView _tvEKYCCamera = (TextView) getView().findViewById(R.id.tvEKYCCamera);
_tvEKYCCamera.setText("Message from Fragment ID type" + " " + fragmentIdTypeMessage.getMessage());
Toast.makeText(getContext(),
"Message Received: " + fragmentIdTypeMessage.getMessage(),
Toast.LENGTH_SHORT).show();
}
#Override
public void onDestroyView() {
super.onDestroyView();
// unregister the registered event.
releaseCameraAndPreview();
GlobalBus.getBus().unregister(this);
}
/**
* Recommended "safe" way to open the camera.
* #param view
* #return
*/
private boolean safeCameraOpenInView(View view) {
boolean qOpened = false;
releaseCameraAndPreview();
mCamera = getCameraInstance();
mCameraView = view;
qOpened = (mCamera != null);
if(qOpened == true){
mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_preview);
preview.addView(mPreview);
mPreview.startCameraPreview();
}
return qOpened;
}
/**
* Safe method for getting a camera instance.
* #return
*/
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
e.printStackTrace();
}
return c; // returns null if camera is unavailable
}
/**
* Clear any existing preview / camera.
*/
private void releaseCameraAndPreview() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if(mPreview != null){
mPreview.destroyDrawingCache();
mPreview.mCamera = null;
}
}
/**
* Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
* excellent StackOverflow answer provided below.
*
* Reference / Credit: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
*/
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
// SurfaceHolder
private SurfaceHolder mHolder;
// Our Camera.
private Camera mCamera;
// Parent Context.
private Context mContext;
// Camera Sizing (For rotation, orientation changes)
private Camera.Size mPreviewSize;
// List of supported preview sizes
private List<Camera.Size> mSupportedPreviewSizes;
// Flash modes supported by this camera
private List<String> mSupportedFlashModes;
// View holding this camera.
private View mCameraView;
public CameraPreview(Context context, Camera camera, View cameraView) {
super(context);
// Capture the context
mCameraView = cameraView;
mContext = context;
setCamera(camera);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setKeepScreenOn(true);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* Begin the preview of the camera input.
*/
public void startCameraPreview()
{
try{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch(Exception e){
e.printStackTrace();
}
}
/**
* Extract supported preview and flash modes from the camera.
* #param camera
*/
private void setCamera(Camera camera)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
mCamera.setParameters(parameters);
}
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null){
mCamera.stopPreview();
}
}
/**
* React to surface changed events
* #param holder
* #param format
* #param w
* #param h
*/
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 {
Camera.Parameters parameters = mCamera.getParameters();
// Set the auto-focus mode to "continuous"
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// Preview size must exist.
if(mPreviewSize != null) {
Camera.Size previewSize = mPreviewSize;
parameters.setPreviewSize(previewSize.width, previewSize.height);
}
mCamera.setParameters(parameters);
mCamera.startPreview();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null){
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
}
/**
* Update the layout based on rotation and orientation changes.
* #param changed
* #param left
* #param top
* #param right
* #param bottom
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
/**
*
* #param sizes
* #param width
* #param height
* #return
*/
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
Camera.Size optimalSize = null;
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) height / width;
// Try to find a size match which suits the whole screen minus the menu on the left.
for (Camera.Size size : sizes){
if (size.height != width) continue;
double ratio = (double) size.width / size.height;
if (ratio <= targetRatio + ASPECT_TOLERANCE && ratio >= targetRatio - ASPECT_TOLERANCE){
optimalSize = size;
}
}
// If we cannot find the one that matches the aspect ratio, ignore the requirement.
if (optimalSize == null) {
// TODO : Backup in case we don't get a size.
}
return optimalSize;
}
}
/**
* Picture Callback for handling a picture capture and saving it out to a file.
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null){
Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
.show();
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
// Restart the camera preview.
safeCameraOpenInView(mCameraView);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/**
* Used to return the camera File output.
* #return
*/
private File getOutputMediaFile(){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MM");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("CameraGuide", "Required media storage does not exist");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
Toast.makeText(getActivity(),"Your picture has been saved!", Toast.LENGTH_LONG).show();
// DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());
return mediaFile;
}
}
Fragment Layout:
RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimary"
>
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/tvEKYCCamera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment" />
<FrameLayout
android:id="#+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center"
>
<View
android:id="#+id/myRectangleView"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#drawable/rectangle"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Place ID Within Here"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="#+id/button_capture"
android:text=""
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="10dp"
android:background="#drawable/camera_button"
/>
</LinearLayout>
Well after some experimenting I figured out that by setting the case Surface.ROTATION_90: while overriding the protected void onLayout(boolean changed, int left, int top, int right, int bottom) solved the orientation issue.
apparently some camera's come with default orientation of 0degree and some 90 degree.
here is what i've changed for people who might face the same issue:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
//added to fix rotation issue
mCamera.setDisplayOrientation(90);
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
//added to fix rotation issue
mCamera.setDisplayOrientation(180);
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
Related
I am trying to set the camera preview in my fragment and add some xml elements like buttons and signs over the preview.
however while using addview the preview of the camera appears different based on devices ratio and camera. sometime it's on top and sometimes it's at bottom pushing the capture button out of the layout.
Note: I've been trying to set the framelayout containing the preview to alignparenttop or centercrop. however the after adding the preview usign addView programatically it keeps showing unaligned while pushing elements off the layout view in certain devices.
meanwhile in some devices it displays the layout aligned to top and elements located over the framlayout.
here is my fragment code
public class KycCameraFragment extends Fragment {
public static final String TAG = "KycCameraFragment";
// Native camera.
private Camera mCamera;
// View to display the camera output.
private CameraPreview mPreview;
// Reference to the containing view.
private View mCameraView;
public KycCameraFragment() {
// Required empty public constructor
}
#Override
public void onStart() {
super.onStart();
// register the event to listen.
GlobalBus.getBus().register(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_kyc_camera, container, false);
// Create our Preview view and set it as the content of our activity.
boolean opened = safeCameraOpenInView(view);
if(opened == false){
Log.d("CameraGuide","Error, Camera failed to open");
return view;
}
// Trap the capture button_round.
Button captureButton = (Button) view.findViewById(R.id.button_capture);
captureButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
return view;
}
#Subscribe(sticky = true)
public void getMessage(Events.FragmentIdTypeMessage fragmentIdTypeMessage) {
TextView _tvEKYCCamera = (TextView) getView().findViewById(R.id.tvEKYCCamera);
_tvEKYCCamera.setText("Message from Fragment ID type" + " " + fragmentIdTypeMessage.getMessage());
Toast.makeText(getContext(),
"Message Received: " + fragmentIdTypeMessage.getMessage(),
Toast.LENGTH_SHORT).show();
}
#Override
public void onDestroyView() {
super.onDestroyView();
// unregister the registered event.
releaseCameraAndPreview();
GlobalBus.getBus().unregister(this);
}
/**
* Recommended "safe" way to open the camera.
* #param view
* #return
*/
private boolean safeCameraOpenInView(View view) {
boolean qOpened = false;
releaseCameraAndPreview();
mCamera = getCameraInstance();
mCameraView = view;
qOpened = (mCamera != null);
if(qOpened == true){
mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_preview);
preview.addView(mPreview);
mPreview.startCameraPreview();
}
return qOpened;
}
/**
* Safe method for getting a camera instance.
* #return
*/
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
}
catch (Exception e){
e.printStackTrace();
}
return c; // returns null if camera is unavailable
}
/**
* Clear any existing preview / camera.
*/
private void releaseCameraAndPreview() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if(mPreview != null){
mPreview.destroyDrawingCache();
mPreview.mCamera = null;
}
}
/**
* Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
* excellent StackOverflow answer provided below.
*
* Reference / Credit: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
*/
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
// SurfaceHolder
private SurfaceHolder mHolder;
// Our Camera.
private Camera mCamera;
// Parent Context.
private Context mContext;
// Camera Sizing (For rotation, orientation changes)
private Camera.Size mPreviewSize;
// List of supported preview sizes
private List<Camera.Size> mSupportedPreviewSizes;
// Flash modes supported by this camera
private List<String> mSupportedFlashModes;
// View holding this camera.
private View mCameraView;
public CameraPreview(Context context, Camera camera, View cameraView) {
super(context);
// Capture the context
mCameraView = cameraView;
mContext = context;
setCamera(camera);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setKeepScreenOn(true);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* Begin the preview of the camera input.
*/
public void startCameraPreview()
{
try{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch(Exception e){
e.printStackTrace();
}
}
/**
* Extract supported preview and flash modes from the camera.
* #param camera
*/
private void setCamera(Camera camera)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
mCamera.setParameters(parameters);
}
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null){
mCamera.stopPreview();
}
}
/**
* React to surface changed events
* #param holder
* #param format
* #param w
* #param h
*/
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 {
Camera.Parameters parameters = mCamera.getParameters();
// Set the auto-focus mode to "continuous"
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// Preview size must exist.
if(mPreviewSize != null) {
Camera.Size previewSize = mPreviewSize;
parameters.setPreviewSize(previewSize.width, previewSize.height);
}
mCamera.setParameters(parameters);
mCamera.startPreview();
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null){
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
}
/**
* Update the layout based on rotation and orientation changes.
* #param changed
* #param left
* #param top
* #param right
* #param bottom
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
Log.d(TAG,"0 degree camera orientation got hit");
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
//added to fix rotation issue
Log.d(TAG,"90 degree camera orientation got hit");
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
//added to fix rotation issue
mCamera.setDisplayOrientation(0);
Log.d(TAG,"180 degree camera orientation got hit");
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
Log.d(TAG,"270 degree camera orientation got hit");
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
/**
*
* #param sizes
* #param width
* #param height
* #return
*/
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
{
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) height / width;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = height;
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;
}
}
/**
* Picture Callback for handling a picture capture and saving it out to a file.
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null){
Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
.show();
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
// Restart the camera preview.
safeCameraOpenInView(mCameraView);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/**
* Used to return the camera File output.
* #return
*/
private File getOutputMediaFile(){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "MM");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("CameraGuide", "Required media storage does not exist");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
Toast.makeText(getActivity(),"Your picture has been saved!", Toast.LENGTH_LONG).show();
// DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());
return mediaFile;
}
}
my fragment XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
>
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/tvEKYCCamera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment" />
<FrameLayout
android:id="#+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#color/black"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="vertical"
android:gravity="center"
>
<View
android:id="#+id/myRectangleView"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#drawable/rectangle"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Place ID Within Here"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center">
<Button
android:id="#+id/button_capture"
android:text=""
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="10dp"
android:background="#drawable/camera_button"
/>
</LinearLayout>
Normally the android camera api with a PreviewCallback freezes the camera after a picture is taken and this can be used as an approval screen for the image taken. My problem is this is not the behavior on phones like the samsung S6 and S6 edge. those two phones continue with the live preview after the picture is taken.
The pictures taken are still good and can be used for later use but the preview isnt showing, and the way my app is set up, i have a check and an x mark for the user to approve what they just took a photo of. Instead they have this overlay for check and "x" and a live preview on the background. And this only happens on the samsung s6/s6 edge.
Any idea what can cause an issue like this on specific devices?
Code used for preview and picture taking with some logging everywhere:
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
// SurfaceHolder
private SurfaceHolder mHolder;
// Our Camera.
private Camera mCamera;
// Parent Context.
private Context mContext;
// Camera Sizing (For rotation, orientation changes)
private Camera.Size mPreviewSize;
// List of supported preview sizes
private List<Camera.Size> mSupportedPreviewSizes;
private List<Camera.Size> mSupportPictureSizes;
// Flash modes supported by this camera
private List<String> mSupportedFlashModes;
// View holding this camera.
private View mCameraView;
public CameraPreview(Context context, Camera camera, View cameraView) {
super(context);
Log.d(TAG, "CameraPreview: ");
// Capture the context
mCameraView = cameraView;
mContext = context;
setCamera(camera);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// mHolder.setKeepScreenOn(true);
// deprecated setting, but required on Android versions prior to 3.0
// mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* Begin the preview of the camera input.
*/
public void startCameraPreview()
{
Log.d(TAG, "startCameraPreview: ");
try{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch(Exception e){
e.printStackTrace();
}
}
/**
* Extract supported preview and flash modes from the camera.
* #param camera
*/
private void setCamera(Camera camera)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportPictureSizes = mCamera.getParameters().getSupportedPictureSizes();
for(Camera.Size size : mSupportedPreviewSizes){
Log.d(TAG, "supportedPreviewSizes width: " + size.width + " x " + size.height);
}
for(Camera.Size size : mSupportPictureSizes){
Log.d(TAG, "mSupportPictureSizes width: " + size.width + " x " + size.height);
}
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
}
mCamera.setParameters(parameters);
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated: ");
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed: ");
if (mCamera != null){
mCamera.stopPreview();
}
}
/**
* React to surface changed events
* #param holder
* #param format
* #param w
* #param h
*/
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
Log.d(TAG, "surfaceChanged: preview surface does not exist");
return;
}
Log.d(TAG, "surfaceChanged: start & stop preview");
// 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 SurfaceHolder getHolder() {
return super.getHolder();
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null){
Log.d(TAG, "onMeasure: w x h: " + width + " x " + height);
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
}
/**
* Update the layout based on rotation and orientation changes.
* #param changed
* #param left
* #param top
* #param right
* #param bottom
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
Log.d(TAG, "onLayout: ");
// Source: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
Log.d(TAG, "onLayout: rotation0");
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
Log.d(TAG, "onLayout: rotation90");
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
Log.d(TAG, "onLayout: rotation180");
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
Log.d(TAG, "onLayout: rotation270");
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
Log.d(TAG, "getOptimalPreviewSize: ");
final double ASPECT_TOLERANCE = 0.05;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Camera.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) {
Log.d(TAG, "getOptimalPreviewSize: found a match");
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
Log.d(TAG, "getOptimalPreviewSize: couldn't find match");
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
Log.d(TAG, "getOptimalPreviewSize: " + optimalSize.width + " x HEIGHT: " + optimalSize.height);
return optimalSize;
}
}
/**
* Picture Callback for handling a picture capture and saving it out to a file.
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size mPictureTakenSize = parameters.getPictureSize();
System.out.println("picture taken size: " + mPictureTakenSize.width + " x " + mPictureTakenSize.height);
if(data!=null){
Log.d(TAG, "onPictureTaken: data not null, setting data to currentData");
// currentData = data;
setPictureTakenData(data);
cameraViewModel.setPreview(true);
}
else{
onProgress = false;
Log.d(TAG, "onPictureTaken: data is null");
}
}
};
I just ran into the same issue. It looks like you can fix it by manually calling camera.stopPreview() in the onPictureTaken method of the PictureCallback.
The camera will not stop preview automatically after picture taken, so you need call mCamera.stopPreview() manually:
if (mCamera != null) {
try {
mCamera.stopPreview();
pauseFlash();
} catch (Exception e) {
e.printStackTrace();
}
}
// if current camera flash mode is TORCH, you need pause it.
private void pauseFlash() {
if (currentFlashMode.equals(Camera.Parameters.FLASH_MODE_TORCH)) {
params = mCamera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(params);
}
}
And then, after do something and navigate back to camera preview:
#Override
public void startCameraPreview() {
if (mCamera != null) {
try {
mCamera.startPreview();
resumeFlash();
} catch (Exception e) {
e.printStackTrace();
// TAKE CARE IF YOUR CAMERA HAS BEEN RELEASED
}
}
}
void resumeFlash() {
flashChange(currentFlashMode);
}
And I have some performance tips: You should find the best picture size and preview size at the first time user use your app, store those in SharedPreferences and you can use after without re-caculate.
Hope this help.
I am creating a custom camera app. The problem I am facing is that the camera preview freezes when I switch between the front and the back camera. When starting the activity with either the front or back camera by calling
boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK)
in the OnCreateView method of the fragment, both cameras will display on startup as expected. As soon as I call the same method in the on click listener of my switch button, the camera freezes immediately.
This is a new implementation which I tried where all the code is within the same fragment instead of a custom class based on the question here: Custom class camera live preview freezes on camera switch yet the result is exactly the same. I am pretty sure I need to do something with the surface view to bind it to the new camera but I am at a loss of how to do this. Anybody have some pointers?
My activity:
public class Camera2ActivityFragment extends Fragment {
// Native camera.
private Camera mCamera;
// View to display the camera output.
private CameraPreview mPreview;
// Reference to the containing view.
private View mCameraView;
/**
* Default empty constructor.
*/
public Camera2ActivityFragment(){
super();
}
/**
* Static factory method
* #param sectionNumber
* #return
*/
public static Camera2ActivityFragment newInstance(int sectionNumber) {
Camera2ActivityFragment fragment = new Camera2ActivityFragment();
//Bundle args = new Bundle();
//args.putInt(ARG_SECTION_NUMBER, sectionNumber);
//fragment.setArguments(args);
return fragment;
}
/**
* OnCreateView fragment override
* #param inflater
* #param container
* #param savedInstanceState
* #return
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_camera2, container, false);
boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK);
if(opened == false){
Log.d("CameraGuide","Error, Camera failed to open");
return view;
}
// Trap the capture button.
Button captureButton = (Button) view.findViewById(R.id.btnCameraStart);
captureButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
Button switchCameraButton = (Button) view.findViewById(R.id.btnSwitchCamera);
switchCameraButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
safeCameraOpenInView(getView(), Camera.CameraInfo.CAMERA_FACING_FRONT); //ISSUE OCCURS HERE!
}
}
);
return view;
}
/**
* Recommended "safe" way to open the camera.
* #param view
* #return
*/
private boolean safeCameraOpenInView(View view, int camID) {
boolean qOpened = false;
releaseCameraAndPreview();
//mCamera = getCameraInstance(Camera.CameraInfo.CAMERA_FACING_BACK);
mCamera = getCameraInstance(camID);
mCameraView = view;
qOpened = (mCamera != null);
if(qOpened == true){
mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_view);
preview.addView(mPreview);
mPreview.startCameraPreview();
}
return qOpened;
}
/**
* Safe method for getting a camera instance.
* #return
*/
public static Camera getCameraInstance(int camID){
Camera c = null;
try {
c = Camera.open(camID); // attempt to get a Camera instance
}
catch (Exception e){
e.printStackTrace();
}
return c; // returns null if camera is unavailable
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
releaseCameraAndPreview();
}
/**
* Clear any existing preview / camera.
*/
private void releaseCameraAndPreview() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if(mPreview != null){
mPreview.destroyDrawingCache();
mPreview.mCamera = null;
}
}
/**
* Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
* excellent StackOverflow answer provided below.
*
* Reference / Credit: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
*/
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
// SurfaceHolder
private SurfaceHolder mHolder;
// Our Camera.
private Camera mCamera;
// Parent Context.
private Context mContext;
// Camera Sizing (For rotation, orientation changes)
private Camera.Size mPreviewSize;
// List of supported preview sizes
private List<Camera.Size> mSupportedPreviewSizes;
// Flash modes supported by this camera
private List<String> mSupportedFlashModes;
// View holding this camera.
private View mCameraView;
public CameraPreview(Context context, Camera camera, View cameraView) {
super(context);
// Capture the context
mCameraView = cameraView;
mContext = context;
setCamera(camera);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setKeepScreenOn(true);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* Begin the preview of the camera input.
*/
public void startCameraPreview()
{
try{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch(Exception e){
e.printStackTrace();
}
}
/**
* Extract supported preview and flash modes from the camera.
* #param camera
*/
private void setCamera(Camera camera)
{
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
parameters.setRotation(90);
//parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
}
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
//mCam = Camera.open();
//mCam.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null){
mCamera.stopPreview();
}
}
/**
* React to surface changed events
* #param holder
* #param format
* #param w
* #param h
*/
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
//Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + 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();
//mCamera.release();
} 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 {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
parameters.setRotation(90);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.enableShutterSound(true);
mCamera.startPreview();
} catch (Exception e){
//Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
/*
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null){
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
*/
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, (int) (width * ratio));
// setMeasuredDimension((int) (width * ratio), height);
}
/**
* Update the layout based on rotation and orientation changes.
* #param changed
* #param left
* #param top
* #param right
* #param bottom
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
/**
*
* #param sizes
* #param width
* #param height
* #return
*/
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
{
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) height / width;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = height;
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;
}
}
/**
* Picture Callback for handling a picture capture and saving it out to a file.
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null){
Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
.show();
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/**
* Used to return the camera File output.
* #return
*/
private File getOutputMediaFile(){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "Pocket Booth");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("Camera Guide", "Required media storage does not exist");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
//DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());
return mediaFile;
}
}
Well, Here I'm going to write a tutorial for you about capturing an
image using Camera by enabling some common features of camera.
Step 1 : Create a preview class
/**
* A basic Camera preview class
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// 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) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.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
// start preview with new settings
startPreview();
}
public void resetCamera(Camera camera) {
mCamera = camera;
}
public void startPreview() {
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
Step 2 : Use FrameLayout to hold the preview.
<FrameLayout
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Step 3 : Get the camera and send to the preview class. you may need to set the parameters that you need prior passing the camera.
/**
* Create our Preview view and set it as the content of UI.
*/
private void initCameraPreview(final int cameraId, final boolean createPreview) {
mCamera = getCameraInstance(cameraId);
setupCameraParameters(cameraId);
if (createPreview) {
mPreview = new CameraPreview(this, mCamera);
mPreviewHolder.addView(mPreview);
}
mReadyToCapture = true;
}
/**
* A safe way to get an instance of the Camera object.
*/
private Camera getCameraInstance(int cameraId) {
Camera c = null;
try {
c = Camera.open(cameraId); // attempt to get a Camera instance
} catch (Exception e) {
e.printStackTrace();
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
/**
* Measure and Setup the camera parameters.
*/
private void setupCameraParameters(int cameraId) {
boolean hasFlash;
Camera.Parameters parameters = mCamera.getParameters();
mPreviewSize = determineBestPreviewSize(parameters);
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
Camera.Size bestPictureSize = determineBestPictureSize(parameters);
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
hasFlash = Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH);
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
hasFlash = Util.hasFrontCameraFlash(parameters);
} else {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
if (hasFlash)
parameters.setFlashMode(mFlashMode);
int[] orientations = Util.getCameraDisplayOrientation(this, cameraId);
mDisplayOrientation = orientations[0];
mLayoutOrientation = orientations[1];
mCamera.setDisplayOrientation(mDisplayOrientation);
mCamera.setParameters(parameters);
}
private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
return determineBestSize(sizes);
}
private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
return determineBestSize(sizes);
}
private Camera.Size determineBestSize(List<Camera.Size> sizes) {
Camera.Size bestSize = null;
for (Camera.Size currentSize : sizes) {
boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
boolean isInBounds = currentSize.width <= PICTURE_SIZE_MAX_WIDTH;
if (isDesiredRatio && isInBounds && isBetterSize) {
bestSize = currentSize;
}
}
if (bestSize == null) {
return sizes.get(0);
}
return bestSize;
}
Step 4 : Writing method for swapping camera
/**
* Swapping between system cameras
*/
private void swapCamera() {
if (!(Camera.getNumberOfCameras() > 1)) {
/* No front facing camera to switch.*/
return;
}
mReadyToCapture = false;
mCamera.stopPreview();
releaseCamera(false);
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
else
mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
initCameraPreview(mCurrentCameraId, false);
mPreview.resetCamera(mCamera);
mPreview.startPreview();
}
Step 5 : Method for toggling flash
/**
* Toggling camera flash to ON/OFF
*/
private void toggleFlash() {
if (Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH)) {
Camera.Parameters parameters = mCamera.getParameters();
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
if (!Util.hasFrontCameraFlash(parameters)) {
/* Front facing camera doesn\'t supported flash. */
return;
}
}
mReadyToCapture = false;
if (Camera.Parameters.FLASH_MODE_ON.equals(parameters.getFlashMode())) {
mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
} else {
mFlashMode = Camera.Parameters.FLASH_MODE_ON;
}
mCameraHandler.post(new Runnable() {
#Override
public void run() {
mCamera.stopPreview();
releaseCamera(false);
initCameraPreview(mCurrentCameraId, false);
mPreview.resetCamera(mCamera);
mPreview.startPreview();
}
});
} else {
/* warning_no_flash */
}
}
Step 6: Methods for handling camera during the states changes of a screen
/**
* Release the camera for other applications
*/
private void releaseCamera(boolean remove) {
if (mCamera != null) {
if (remove)
mPreview.getHolder().removeCallback(mPreview);
mCamera.release();
mCamera = null;
}
}
Step 7: Utility classes.
/**
* Check whether the given feature available in s/m
*
* #return Returns true if the devices supports the feature, else
* false.
*/
public static boolean hasSystemFeature(Context context, String feature) {
return context.getPackageManager().hasSystemFeature(feature);
}
/**
* Check whether front camera flash feature available in s/m
*/
public static boolean hasFrontCameraFlash(Camera.Parameters cameraParameters) {
boolean result = true;
if (cameraParameters.getFlashMode() == null) {
result = false;
}
List<String> supportedFlashModes = cameraParameters.getSupportedFlashModes();
if (supportedFlashModes == null || supportedFlashModes.isEmpty()
|| supportedFlashModes.size() == 1 &&
supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
result = false;
}
return result;
}
/**
* Showing camera in the same orientation as the display
*/
public static int[] getCameraDisplayOrientation(Activity activity,
int cameraId) {
Camera.CameraInfo info =
new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
return new int[]{result, degrees};
}
Step 8: Capturing
// Get an image from the camera
if (null != mCamera && mReadyToCapture) {
mCameraOrientationListener.rememberOrientation();
mCamera.takePicture(mShutter, null, mPicture)
}
/**
* Camera shutter sound callback,
* used to enable sound while capture
*/
private Camera.ShutterCallback mShutter = new Camera.ShutterCallback() {
#Override
public void onShutter() {
}
};
/**
* Camera picture callback
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
mReadyToCapture = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
int rotation = ((mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ? mDisplayOrientation :
((360 - mDisplayOrientation) % 360)) + mCameraOrientationListener.getRememberedOrientation()
+ mLayoutOrientation) % 360;
if (rotation != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
}
};
Step 9: Camera orientation listener for handling image rotation
/**
* Orientation listener to remember the device's orientation when the user presses
* the shutter button.
* <p/>
* The orientation will be normalized to return it in steps of 90 degrees
* (0, 90, 180, 270).
*/
public class CameraOrientationListener extends OrientationEventListener {
private int currentNormalizedOrientation;
private int rememberedNormalizedOrientation;
public CameraOrientationListener(Context context) {
super(context, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onOrientationChanged(int orientation) {
if (orientation != ORIENTATION_UNKNOWN) {
currentNormalizedOrientation = normalize(orientation);
}
}
private int normalize(int degrees) {
if (degrees > 315 || degrees <= 45) {
return 0;
}
if (degrees > 45 && degrees <= 135) {
return 90;
}
if (degrees > 135 && degrees <= 225) {
return 180;
}
if (degrees > 225 && degrees <= 315) {
return 270;
}
throw new RuntimeException("The physics as we know them are no more. Watch out for anomalies.");
}
public void rememberOrientation() {
rememberedNormalizedOrientation = currentNormalizedOrientation;
}
public int getRememberedOrientation() {
return rememberedNormalizedOrientation;
}
}
Step 10: States handling
#Override
public void onPause() {
super.onPause();
mReadyToCapture = false;
releaseCamera(true);
}
#Override
public void onResume() {
super.onResume();
removePreview();
mReadyToCapture = false;
smoothCameraLoading();
}
private void removePreview() {
mPreviewHolder.removeAllViews();
}
private void smoothCameraLoading() {
mCameraHandler.post(new Runnable() {
#Override
public void run() {
initCameraPreview(mCurrentCameraId, true);
}
});
}
Step 11: Instance variable used
private String mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
private int mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
private int mDisplayOrientation;
private int mLayoutOrientation;
private boolean mReadyToCapture = false;
private Camera.Size mPreviewSize;
private FrameLayout mPreviewHolder;
private Camera mCamera;
private CameraPreview mPreview;
private Handler mCameraHandler;
private CameraOrientationListener mCameraOrientationListener;
private FrameLayout mRootView;
In my camera app, it works with somethings different from yours:
1. Open camera when surfaceCreated.
2. StartPreview and no stopPreview in surfaceChanged.
3. Release camera in surfaceDestoryed.
To change the camera, simply set the surfaceView to INVISIBLE, then set your camera parameters, then set the surfaceView to VISIBLE again. In my app, I only do it like this:
mbtCamera.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(cameraMax > 1){
mSurfaceView.setVisibility(View.INVISIBLE);
if(mCameraParam.isZoomSupported()) cameraZoomRatios.clear();
if(cameraUsing == 0) cameraUsing = 1; else cameraUsing = 0;
mCameraOverlayView.setScaleFactor(1.0f);
mSurfaceView.setVisibility(View.VISIBLE);
}
}
});
Hope this will help!
The complete callback:
class CameraSurfaceHolderCallback implements SurfaceHolder.Callback {
#Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open(cameraUsing);
mCameraParam = mCamera.getParameters();
supportFlashMode = mCameraParam.getSupportedFlashModes();
if(supportFlashMode == null) mbtFlash.setVisibility(View.INVISIBLE);
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraUsing, info);
int rotation = 0;
if (info.facing == CameraInfo.CAMERA_FACING_FRONT){
rotation = (info.orientation - rt + 360) % 360;
if(mCameraParam.getSupportedFlashModes() == null) mbtFlash.setVisibility(View.INVISIBLE);
}
else{ // back-facing camera
rotation = (info.orientation + rt) % 360;
mbtFlash.setVisibility(View.VISIBLE);
if(flashMode.equals(Camera.Parameters.FLASH_MODE_AUTO)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_automatic);
else if(flashMode.equals(Camera.Parameters.FLASH_MODE_OFF)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_off);
else if(flashMode.equals(Camera.Parameters.FLASH_MODE_ON)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_on);
Matrix rot = new Matrix();
rot.setRotate(360-rt, icons[0].getWidth()/2, icons[0].getHeight()/2);
icons[0] = Bitmap.createBitmap(icons[0], 0, 0, icons[0].getWidth(), icons[0].getHeight(), rot, true);
mbtFlash.setImageBitmap(icons[0]);
mCameraParam.setFlashMode(flashMode);
}
if(mCameraParam.isZoomSupported()){
cameraZoomRatios = mCameraParam.getZoomRatios();
mCameraOverlayView.setMaxScaleFactor(cameraZoomRatios.get(mCameraParam.getMaxZoom())/100f);
}else mCameraOverlayView.setMaxScaleFactor(1.0f);
List<Size> ss = mCameraParam.getSupportedPictureSizes();
Size maxResolution = ss.get(0);
long pixel1, pixel2;
pixel1 = maxResolution.width * maxResolution.height;
for(int i=0; i<ss.size(); i++){
pixel2 = ss.get(i).width * ss.get(i).height;
if(pixel2 > pixel1){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
mCameraParam.setPictureSize(maxResolution.width, maxResolution.height);
mCameraParam.setJpegQuality(100);
LayoutParams rlParams = (LayoutParams) mSurfaceView.getLayoutParams();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
Display dp = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
dp.getMetrics(dm);
float dpiScale = dm.density;
float wScaleFactor = (dm.widthPixels-10*dpiScale)/maxResolution.height;
float hScaleFactor = (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale)/maxResolution.width;
if(wScaleFactor < hScaleFactor){
rlParams.width = (int) (dm.widthPixels - 10*dpiScale);
rlParams.height = (int) (maxResolution.width * wScaleFactor);
}else{
rlParams.width = (int) (maxResolution.height * hScaleFactor);
rlParams.height = (int) (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale);
}
mSurfaceView.setLayoutParams(rlParams);
mCameraOverlayView.setLayoutParams(rlParams);
ss = mCameraParam.getSupportedJpegThumbnailSizes();
float photoAspectRatio, thumbAspectRatio;
photoAspectRatio = (float)maxResolution.width / maxResolution.height;
thumbAspectRatio = 0;
pixel1 = 0;
for(int i=0; i<ss.size(); i++){
if(ss.get(i).height != 0) thumbAspectRatio = (float)ss.get(i).width / ss.get(i).height;
if(thumbAspectRatio == photoAspectRatio){
if(pixel1 == 0)
{
maxResolution = ss.get(i);
pixel1 = ss.get(i).width * ss.get(i).height;
}else{
pixel2 = ss.get(i).width * ss.get(i).height;
if((pixel2 < pixel1)&&(pixel2 != 0)){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
}
}
if(pixel1 != 0){
mCameraParam.setJpegThumbnailSize(maxResolution.width, maxResolution.height);
mCameraParam.setJpegThumbnailQuality(100);
}
List<String> focusModes = mCameraParam.getSupportedFocusModes();
if(focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)){
mCameraParam.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
focusingColor = Color.YELLOW;
}
int minEv = mCameraParam.getMinExposureCompensation();
int maxEv = mCameraParam.getMaxExposureCompensation();
if((minEv == 0)&&(maxEv == 0)) mCameraOverlayView.setEVCompensate(false);
else mCameraOverlayView.setEVCompensate(minEv, maxEv);
mCameraParam.setRotation(rotation);
mCamera.setParameters(mCameraParam);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
try {
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mSurfaceView.getHolder());
List<Size> ss = mCameraParam.getSupportedPreviewSizes();
Size maxResolution = ss.get(0);
long pixel1, pixel2;
float photoAspectRatio, previewAspectRatio;
photoAspectRatio = (float)mCameraParam.getPictureSize().width / mCameraParam.getPictureSize().height;
previewAspectRatio = 0;
pixel1 = 0;
for(int i=0; i<ss.size(); i++){
if(ss.get(i).height != 0) previewAspectRatio = (float)ss.get(i).width / ss.get(i).height;
if(previewAspectRatio == photoAspectRatio){
if(pixel1 == 0)
{
maxResolution = ss.get(i);
pixel1 = ss.get(i).width * ss.get(i).height;
}else{
pixel2 = ss.get(i).width * ss.get(i).height;
if(pixel2 > pixel1){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
}
}
if(pixel1 != 0) mCameraParam.setPreviewSize(maxResolution.width, maxResolution.height);
mCamera.setParameters(mCameraParam);
mCamera.startPreview();
}
catch(Exception e){}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
}
}
I used it in activity and no fragment. I am not sure this will make something different.
I think that there is a little problem in the safeCameraOpenInView method and that is:
Your new camera view is made under the frozen picture.
So you should edit this line: preview.addView(mPreview);
as this: preview.addView(mPreview, preview.getChildCount());
Or you can first delete the previous camera view and then add new one :
preview.removeAllViews();
preview.addView(mPreview);
Hope that this will work.
I want to get a full screen portrait camera preview. Using Google's sample, the preview is in landscape format across the portrait screen. When I set to the preview to 480 x 864, the reverse of working landscape dimensions, I get a setParameters failed error.
I have read all the StackOverflow I can find, but I can't figure why dimensions supported in landscape fail in portrait. Nor can I find a way to force full screen portrait preview. The Camera app on the phone provides full screen portrait preview so I know it is not a hardware limitation.
I'm testing with a Droid 3 running Android 2.3.3
I would appreciate any suggestions.
public class SimplePreview extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "Preview";
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Size mPreviewSize;
List<Size> mSupportedPreviewSizes;
Camera mCamera;
int cameraId;
Activity activity;
public SimplePreview(Activity context, int defaultCamera) {
super(context);
activity = context;
cameraId = defaultCamera;
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
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();
}
}
public void switchCamera(Camera camera) {
setCamera(camera);
try {
camera.setPreviewDisplay(mHolder);
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
camera.setParameters(parameters);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a
// wrapper to a SurfaceView that centers the camera preview instead
// of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
Log.i(TAG, "setting View measured dimensions to width: " + width
+ " height: " + height);
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 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) {
Log.v(TAG, " width: " + size.width + " height: " + size.height);
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);
}
}
}
Log.i(TAG, "optimal preview width: " + optimalSize.width + " height: "
+ optimalSize.height);
return optimalSize;
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
final int width = getWidth();
final int height = getHeight();
Log.i(TAG, "view width: " + width + " height: " + height);
if (height > width) {
Log.i(TAG, "in portrait mode so rotate camera preview");
// THis line fixed the camera display orientation. seems to
// have to be called before setPreviewDisplay()
mCamera.setDisplayOrientation(90);
}
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
/**
* orientation and rotation work done here. Now that the size is known, set
* up the camera parameters and begin the preview.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mCamera != null) {
mCamera.stopPreview();
Camera.Parameters parameters = mCamera.getParameters();
// mCamera.setDisplayOrientation(90);
final int width = getWidth();
final int height = getHeight();
Log.i(TAG, "view width: " + width + " height: " + height);
if (height > width) {
Log.i(TAG, "portrait: setting preview width: " + 480
+ " height: " + 864);
parameters.setPreviewSize(480, 864);
// had no effect
parameters.set("orientation", "portrait");
} else {
Log.i(TAG, "landscape: setting preview width: "
+ mPreviewSize.width + " height: "
+ mPreviewSize.height);
parameters.setPreviewSize(mPreviewSize.width,
mPreviewSize.height);
}
requestLayout();
// *** following line throws setParameters failed error ***
mCamera.setParameters(parameters);
mCamera.startPreview();
}
}
}
And the call from the Activity's onCreate():
preview = new SimplePreview(this, defaultCameraId);
setContentView(preview);
try this code:
package com.example.dragme;
import java.io.IOException;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.os.Build;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
public Camera mCamera;
private static boolean DEBUGGING = true;
private static final String LOG_TAG = "CameraPreviewSample";
private static final String CAMERA_PARAM_ORIENTATION = "orientation";
private static final String CAMERA_PARAM_LANDSCAPE = "landscape";
private static final String CAMERA_PARAM_PORTRAIT = "portrait";
protected Activity mActivity;
protected List<Camera.Size> mPreviewSizeList;
protected List<Camera.Size> mPictureSizeList;
protected Camera.Size mPreviewSize;
protected Camera.Size mPictureSize;
public CameraPreview(Context context, Camera camera) {
super(context);
mActivity=(Activity)context;
mCamera = camera;
// 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) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d("CameraView", "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
// start preview with new settings
try {
Camera.Parameters cameraParams = mCamera.getParameters();
boolean portrait = isPortrait();
configureCameraParameters(cameraParams, portrait);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d("CameraView", "Error starting camera preview: " + e.getMessage());
}
}
public void onPause() {
if (null == mCamera) {
return;
}
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
protected void configureCameraParameters(Camera.Parameters cameraParams, boolean portrait) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { // for 2.1 and before
if (portrait) {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_PORTRAIT);
} else {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_LANDSCAPE);
}
} else { // for 2.2 and later
int angle;
Display display = mActivity.getWindowManager().getDefaultDisplay();
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;
}
Log.v(LOG_TAG, "angle: " + angle);
mCamera.setDisplayOrientation(angle);
}
cameraParams.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
cameraParams.setPictureSize(mPictureSize.width, mPictureSize.height);
if (DEBUGGING) {
Log.v(LOG_TAG, "Preview Actual Size - w: " + mPreviewSize.width + ", h: " + mPreviewSize.height);
Log.v(LOG_TAG, "Picture Actual Size - w: " + mPictureSize.width + ", h: " + mPictureSize.height);
}
mCamera.setParameters(cameraParams);
}
public boolean isPortrait() {
return (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
}
}
I'm creating a camera app that implements it's own camera preview for taking pictures.
The app is currently forced into portrait mode.
My problem is that the preview from the camera is slightly stretched (the aspect ratio is a bit off).
The funny thing is that I'm setting my SurfaceView size to always match the preview size. This ensures that the aspect ratio should always be perserved... but it isn't...
Here is the layout i use to show my camera preview:
public class Cp extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "CameraPreview";
private boolean mPreviewRunning = false;
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Size mPreviewSize;
private List<Size> mSupportedPreviewSizes;
private Camera mCamera;
public boolean IsPreviewRunning() {
return mPreviewRunning;
}
public Cp(Context context) {
this(context, null, 0);
}
public Cp(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Cp(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
requestLayout();
}
}
public void switchCamera(Camera camera) {
setCamera(camera);
try {
camera.setPreviewDisplay(mHolder);
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
camera.setParameters(parameters);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a wrapper to
// a SurfaceView that
// centers the camera preview instead of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes == null && mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
}
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.height;
previewHeight = mPreviewSize.width;
}
if (previewWidth == 0) {
previewWidth = 1;
}
if (previewHeight == 0) {
previewHeight = 1;
}
// 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);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where to
// draw.
try {
if (mCamera != null) {
Parameters params = mCamera.getParameters();
mSupportedPreviewSizes = params.getSupportedPreviewSizes();
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
stop();
}
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;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mCamera != null) {
// Now that the size is known, set up the camera parameters and
// begin the preview.
Camera.Parameters parameters = mCamera.getParameters();
if (mPreviewSize != null) {
parameters.setPreviewSize(mPreviewSize.width,
mPreviewSize.height);
}
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
mPreviewRunning = true;
}
}
public void stop() {
if (mCamera != null) {
mCamera.stopPreview();
mPreviewRunning = false;
mCamera = null;
}
}
}
Please note that in onLayout width and height are swapped because the app is always running in portrait.
I'm including some pictures to show you what the problem looks like:
I've debugged the code. The preview size is being set to 1280x720 and the size of the layout is also being correctly adjusted to match that size.
I've also tried different layouts but the result was always the same...
I was having the same problem, after some days in this puzzle, my Java class end up on this code:
So, The problem was happening because the camera display had a size (hight x width) 576x720, and my display was 1184x720. So, the camera preview (my surface view class) stretched to fill in the parent.
So, the approach that worked is to make this view bigger than my screen, and that just display the area of my screen. So, I had to use this weird frame combination (a relative layout inside a linearlayout), so that the size of the relative will be as big as I want - the surface view will fill in it -, and the linear will take just the part that I wanna display.
package com.example.frame.camera;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.example.frame.MainActivity;
public class NativeCamera extends SurfaceView implements SurfaceHolder.Callback {
static private NativeCamera instance;
private LinearLayout frame = null;
private RelativeLayout innerFrame = null;
private Camera camera;
private final SurfaceHolder previewHolder;
private static final String TAG = "NativeCamera.java";
private boolean inPreview = false;
private boolean cameraConfigured = false;
private boolean frontCamera = false;
private Camera.Size size;
static public NativeCamera getInstance() {
if (NativeCamera.instance == null) {
if (MainActivity.debug) {
Log.d(TAG, "Creating Camera Singleton");
}
NativeCamera.instance = new NativeCamera(MainActivity.instance);
}
return NativeCamera.instance;
}
public void onResume() {
if (MainActivity.debug) {
Log.d(TAG, "onResume");
}
camera = Camera.open();
if (size != null) {
initPreview(size.width, size.height);
}
startPreview();
}
public void onPause() {
if (MainActivity.debug) {
Log.d(TAG, "onPause");
}
if (inPreview) {
camera.stopPreview();
}
camera.release();
camera = null;
inPreview = false;
}
public void onDestroy() {
if (MainActivity.debug) {
Log.d(TAG, "onDestroy");
}
NativeCamera.instance = null;
}
public void onSwitch() {
frontCamera = !frontCamera;
if (inPreview) {
camera.stopPreview();
}
camera.release();
int cam = frontCamera ? 1 : 0;
camera = Camera.open(cam);
if (size != null) {
initPreview(size.width, size.height);
}
startPreview();
}
private NativeCamera(Context context) {
super(context);
// TODO Auto-generated constructor stub
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
previewHolder = getHolder();
previewHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
// previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
innerFrame = new RelativeLayout(MainActivity.instance);
innerFrame.addView(this);
frame = new LinearLayout(MainActivity.instance);
frame.addView(innerFrame);
}
public LinearLayout getFrame() {
return frame;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
if (MainActivity.debug) {
Log.d(TAG, "surfaceChanged");
}
initPreview(width, height);
startPreview();
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
// no-op -- wait until surfaceChanged()
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// no-op
}
private Camera.Size getBestPreviewSize(int width, int height,
Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
this.size = result;
return (result);
}
private void initPreview(int width, int height) {
if (camera != null && previewHolder.getSurface() != null) {
if (!cameraConfigured) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
parameters.setPreviewSize(size.width, size.height);
camera.setParameters(parameters);
cameraConfigured = true;
// Setting up correctly the view
double ratio = size.height / (double) size.width;
LayoutParams params = innerFrame.getLayoutParams();
params.height = MainActivity.size.y;
params.width = (int) (MainActivity.size.y * ratio);
innerFrame.setLayoutParams(params);
int deslocationX = (int) (params.width / 2.0 - MainActivity.size.x / 2.0);
innerFrame.animate().translationX(-deslocationX);
}
}
try {
camera.setPreviewDisplay(previewHolder);
camera.setDisplayOrientation(90);
} catch (Throwable t) {
Log.e(TAG, "Exception in setPreviewDisplay()", t);
Toast.makeText(MainActivity.instance, t.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
private void startPreview() {
if (MainActivity.debug) {
Log.d(TAG, "startPreview");
}
if (cameraConfigured && camera != null) {
camera.startPreview();
inPreview = true;
}
}
}
I have been trying to solve the same issue... Below code worked for me:
private void setMyPreviewSize(int width, int height) {
// Get the set dimensions
float newProportion = (float) width / (float) height;
// Get the width of the screen
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
float screenProportion = (float) screenWidth / (float) screenHeight;
// Get the SurfaceView layout parameters
android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
if (newProportion > screenProportion) {
lp.width = screenWidth;
lp.height = (int) ((float) screenWidth / newProportion );
} else {
lp.width = (int) (newProportion * (float) screenHeight);
lp.height = screenHeight;
}
// Commit the layout parameters
surfaceView.setLayoutParams(lp);
}
You can see the thread: Resizing surface view for aspect ratio change in video display in android
Here's a solution
surfaceChanged is just to get the best preview size
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.e("MYTAG", "surfaceChanged " );
Camera.Parameters myParameters = camera.getParameters();
Camera.Size myBestSize = getBestPreviewSize(width, height, myParameters);
if(myBestSize != null){
myParameters.setPreviewSize(myBestSize.width, myBestSize.height);
camera.setParameters(myParameters);
camera.startPreview();
}
}
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageReview.setImageBitmap(bitmap);
}
This will set the captured image on imageReview, but you need a method to get the rotation of the bitmap
you need to set imageView scaleType attribute for stretched image issue
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
/>