카메라를 활용하는 방법은 2가지 이다.
- 기본 카메라 어플의 Activity를 활용해서 사진을 찍고 파일 처리
- Camera API를 이용하여 직접 내장카메라를 조작
1. Android manifest permission 수정
2. Camera API 이용
3. SurfaceView
[Android manifest permission 수정]
카메라 활용을 위해서 permission 태그를 추가한다.
<uses-permission/>은 알겠는데 <uses-feature/>는 또 처음 등장하는 개념이다.
feature는 해당 기능에 대한 H/W 적인 지원이 가능한지에 대한 여부를 묻는다고 보면 된다.
어차피 지금 만들어지는 스마트폰에서 카메라가 없을리는 만무하니까 해당 태그는 그냥 지워버리자.
[Camera API 이용] Camera API 는 제조사들의 카메라 성능 향상으로 인해서 롤리팝(5.0) 이상의 안드로이드부터는 기존 API와는 전혀 별개의 Camera API2가 새로이 만들어져서 제공되고 있다.
하지만 해당 기능을 모두 활용하기 위해선 터무니 없이 높아진 추상화 레벨에 대한 이해가 필요하고, 단순한 사진찍기 기능 구현에도 엄청난 양의 소스코드가 추가되므로,
Camera API 를 활용해서 앱을 만들어 보도록 한다.
[SurfaceView]
애니메이션 화면 표현에서 가장 필수적인 기술요소가 있다면 Double Buffering일 것이다.
사용자의 눈을 통해서 보여지는 Display가 보여줄 화면을 완성하기도 전에 그려지고 있는 모습을 뿌려준다면 사용자는 애니메이션이 매끄럽지 못하다고 느낄 것이다.
이를 해결하기 위해서 그리기가 완성되어 사용자의 눈에 보여지는 View 외에, 다음번에 보여질 이미지를 그리기 위한 View를 메모리에 올려서 처리하게 된다.
이렇게 뒤에서 이미지를 그려주는 기술을 Double Buffering 이라고 하는데 안드로이드 Camera 에서는 SurfaceView라는 클래스가 이것을 지원 해 준다.
SurfaceView는 SurfaceHolder와 SurfaceHolder.Collback 클래스를 이용하여 Double Buffering 처리를 구현해야 하는데, 더욱 멋진 것은 Camera API가 Preview 화면을 이미 그 처리를 완료해서 우리에게 SurfaceView만 넘겨준다는 것이다.
완성된 CameraView 소스코드
- 기본 카메라 어플의 Activity를 활용해서 사진을 찍고 파일 처리
- Camera API를 이용하여 직접 내장카메라를 조작
1. Android manifest permission 수정
2. Camera API 이용
3. SurfaceView
[Android manifest permission 수정]
카메라 활용을 위해서 permission 태그를 추가한다.
<uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission/>은 알겠는데 <uses-feature/>는 또 처음 등장하는 개념이다.
feature는 해당 기능에 대한 H/W 적인 지원이 가능한지에 대한 여부를 묻는다고 보면 된다.
어차피 지금 만들어지는 스마트폰에서 카메라가 없을리는 만무하니까 해당 태그는 그냥 지워버리자.
[Camera API 이용] Camera API 는 제조사들의 카메라 성능 향상으로 인해서 롤리팝(5.0) 이상의 안드로이드부터는 기존 API와는 전혀 별개의 Camera API2가 새로이 만들어져서 제공되고 있다.
하지만 해당 기능을 모두 활용하기 위해선 터무니 없이 높아진 추상화 레벨에 대한 이해가 필요하고, 단순한 사진찍기 기능 구현에도 엄청난 양의 소스코드가 추가되므로,
Camera API 를 활용해서 앱을 만들어 보도록 한다.
[SurfaceView]
애니메이션 화면 표현에서 가장 필수적인 기술요소가 있다면 Double Buffering일 것이다.
사용자의 눈을 통해서 보여지는 Display가 보여줄 화면을 완성하기도 전에 그려지고 있는 모습을 뿌려준다면 사용자는 애니메이션이 매끄럽지 못하다고 느낄 것이다.
이를 해결하기 위해서 그리기가 완성되어 사용자의 눈에 보여지는 View 외에, 다음번에 보여질 이미지를 그리기 위한 View를 메모리에 올려서 처리하게 된다.
이렇게 뒤에서 이미지를 그려주는 기술을 Double Buffering 이라고 하는데 안드로이드 Camera 에서는 SurfaceView라는 클래스가 이것을 지원 해 준다.
SurfaceView는 SurfaceHolder와 SurfaceHolder.Collback 클래스를 이용하여 Double Buffering 처리를 구현해야 하는데, 더욱 멋진 것은 Camera API가 Preview 화면을 이미 그 처리를 완료해서 우리에게 SurfaceView만 넘겨준다는 것이다.
완성된 CameraView 소스코드
package com.example.ch4_camera; import android.app.Activity; import android.hardware.Camera; import android.os.Environment; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.io.File; import java.io.FileOutputStream; import java.util.List; /** * Created by student on 2016-08-04. */public class MyCameraView extends SurfaceView implements SurfaceHolder.Callback{ Activity context; /** * surface 작업자 * 일반적으로는 holder로 surface점유 * drawing이 되지만 camera class에서는 이미 surface로 그린상태로 * 넘기기 때문에 넘어오는 surface를 받기만 한다 */ SurfaceHolder holder; Camera camera; List<Camera.Size> supportedPreviewSizes; Camera.Size previewSize; int width; int height; public MyCameraView(Activity context){ super(context); this.context = context; // surfaceview만 나오면 무조건 나오는 2줄의 소스코드 holder = getHolder(); holder.addCallback(this); } // surface가 최초로 준비되는 순간 딱 한번 호출 @Override public void surfaceCreated(SurfaceHolder holder) { // camera 점유 camera = Camera.open(); // surface에 직접 그리는게 아니라 camera에서부터 넘어오는 것을 그대로 받는것 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); try{ // camera의 surface가 holder에게 전달되게 연결 camera.setPreviewDisplay(holder); // camera 설정 Camera.Parameters parameters = camera.getParameters(); /** * preview 사이즈를 개발자가 임의 숫자로 주면 에러 나기때문에 * 꼭 camera에서 지원하는 size목록 중 하나를 대입해야 한다. */ supportedPreviewSizes = parameters.getSupportedPreviewSizes(); /** * camera에서 제공하는 preview사이즈를 그대로 대입하는게 아니라 * 폰의 사이즈와 대비해서 계산 */ if(supportedPreviewSizes != null){ previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height); } }catch(Exception e){ camera.release(); camera = null; } } /** * surface공간의 사이즈가 변경될때마다 반복호출 * 일반적으로는 created호출 후 바로 호출된후 다시 호출은 안된다. */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(previewSize.width, previewSize.height); setCameraDisplayOrientation(context, 0); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); camera.setParameters(parameters); // preview 출력 시작 camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if(camera != null){ camera.stopPreview(); camera.release(); camera = null; } } // 사진 찍기 위한 개발자 함수. activity에서 호출할 것임 public void capture(){ if(camera != null){ camera.takePicture(null, null, new Camera.PictureCallback(){ @Override public void onPictureTaken(byte[] data, Camera camera) { // file write FileOutputStream fos; try{ File dir = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + "/multi"); if(!dir.exists()){ dir.mkdir(); } // createTempFile( 파일명 prefix, 파일 확장자, 파일 저장폴더) File file = File.createTempFile("mulcam", ".jpg", dir); if(!file.exists()){ file.createNewFile(); } fos = new FileOutputStream(file); fos.write(data); fos.flush(); fos.close(); Log.d("mulcam", file.getAbsolutePath()); }catch (Exception e){ e.printStackTrace(); } // 사진을 찍고 나면 찍은이미지에서 화면이 멈춰있기때문에 연속적인 촬영을 위해 camera.startPreview(); } }); } } 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.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); } } 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; } private void setCameraDisplayOrientation(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; } camera.setDisplayOrientation(result); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); setMeasuredDimension(width, height); } }
댓글 없음:
댓글 쓰기