위의 페이지를 보니 카메라를 찍을때는 2가지를 고려해야 합니다.
1. Preview(사진 미리보기)
2. 저장위치
파일은 총 3개 구성입니다. (preview, xml 화면, activity)
Activity는 하나면 되고 그위에 프리뷰를 FrameLayout를 이용해 붙이는 식으로 되어있습니다. 이 소스에는 가로, 세로모드를 지원하지만 핸드폰설정에 '화면 회전'을 꺼둔 상태라면 되지 않습니다.(만약 필요하시다면 화면회전을 여시는 소스를 추가하셔야 합니다~)
절차는 다음과 같습니다.
1. 카메라 하드웨어 유무 확인 및 액세스 가능한지 확인
2. 프리뷰 클래스 생성
3. 촬영 시작용 리스너 생성
4. 캡쳐후 정리
5. 카메라 리소스 반환
1. 프리뷰 클래스 생성
카메라 프리뷰 클래스는 SurfaceView 클래스를 상속받아서 뷰 레이아웃안에 들어갈 대상 클래스입니다. 프리뷰가 생성/파괴시, 혹은 해상도 변경, 화면 회전등에 대한 프리뷰 변경을 위한 콜백 인터페이스를 구현해야 하므로 SurfaceHolder.Callback를 implement 합니다.
프리뷰 클래스는 다음과 같습니다.
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
String TAG = "CAMERA";
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// SurfaceHolder 가 가지고 있는 하위 Surface가 파괴되거나 업데이트 될경우 받을 콜백을 세팅한다
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated 되었지만 3.0 이하 버젼에서 필수 메소드라서 호출해둠.
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Surface가 생성되었으니 프리뷰를 어디에 띄울지 지정해준다. (holder 로 받은 SurfaceHolder에 뿌려준다.
try {
Camera.Parameters parameters = mCamera.getParameters();
if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
parameters.setRotation(90);
} else {
parameters.set("orientation", "landscape");
mCamera.setDisplayOrientation(0);
parameters.setRotation(0);
}
mCamera.setParameters(parameters);
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 프리뷰 제거시 카메라 사용도 끝났다고 간주하여 리소스를 전부 반환한다
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
@Override
private Camera.Size getBestPreviewSize(int width, int height)
{
Camera.Size result=null;
Camera.Parameters p = mCamera.getParameters();
for (Camera.Size size : p.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;
}
}
}
}
return result;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// 프리뷰를 회전시키거나 변경시 처리를 여기서 해준다.
// 프리뷰 변경시에는 먼저 프리뷰를 멈춘다음 변경해야한다.
if (mHolder.getSurface() == null){
// 프리뷰가 존재하지 않을때
return;
}
// 우선 멈춘다
try {
mCamera.stopPreview();
} catch (Exception e){
// 프리뷰가 존재조차 하지 않는 경우다
}
// 프리뷰 변경, 처리 등을 여기서 해준다.
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size size = getBestPreviewSize(w, h);
parameters.setPreviewSize(size.width, size.height);
mCamera.setParameters(parameters);
// 새로 변경된 설정으로 프리뷰를 재생성한다
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
여기서 getBestPreviewSize() 메소드는 꼭 짤필요는 없고 유저가 선택하도록 만들어도 되는데 귀찮으면 복붙~
그 다음에는 만든 프리뷰 클래스를 메인 레이어에 삽입해줍니다. 프리뷰 위에 다른 버튼등의 UI 컴포넌트들을 배치
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<Button
android:id="@+id/button_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical = "true"
android:text="Capture" />
</RelativeLayout>
카메라의 기본모드는 가로를 정상모드로 간주하기 때문에 여기 예제도 일단 가로모드 UI로 만들어두지만, 실제 카메라를 다룰때는 기기 상태에 따라 가로 세로 UI가 변하도록 만들어 줘야 합니다.
2. 메인 엑티비티 구성
카메라를 사용하려면 카메라를 사용할 수 있는지 확인을 해야합니다. 카메라는 기기에 한개밖에 없기때문에 다른 어플에서 사용중인지 확인해야하고, 사용후 반환하지 않으면 다른 앱에서 카메라를 사용할 수 없습니다.(심할경우는 RuntimeException 을 발생) 그러므로 주의해야합니다~!
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
// 카메라가 최소한 한개 있는 경우 처리
Log.i(TAG, "Number of available camera : "+Camera.getNumberOfCameras());
return true;
} else {
// 카메라가 전혀 없는 경우
Toast.makeText(mContext, "No camera found!", Toast.LENGTH_SHORT).show();
return false;
}
}
사용여부를 확인했으니 카메라 인스턴스를 호출합니다.
public static Camera getCameraInstance(){
Camera c = null;
try {
c = Camera.open();
}
catch (Exception e){
// 사용중이거나 사용 불가능 한 경우
}
return c;
}
open() 의 매개변수로 int 값을 받을 수도 있는데, 일반적으로 0이 후면 카메라, 1이 전면 카메라를 의미합니다.
보다 명확히 하자면, 카메라 id 값의 범위는 0 부터 Camera.getNumberOfCameras()-1 까지 인데, 대체로 Camera.getNumberOfCameras() 가 2 입니다(폰의 경우 대게 전 후 각각 한개씩 카메라가 있으므로 그런것)
위의 메소드를 합쳐서 아래처럼 코딩합니다. (파일 저장 기능도 추가됨.)
public class CameraAction extends Activity{
private static String TAG = "CAMERA";
private Context mContext = this;
private Camera mCamera;
private CameraPreview mPreview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.camera_preview);
mContext = this;
// 카메라 사용여부 체크
if(!checkCameraHardware(getApplicationContext())){
finish();
}
// 카메라 인스턴스 생성
mCamera = getCameraInstance();
// 프리뷰창을 생성하고 액티비티의 레아이웃으로 지정
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
// 촬영버튼 등록
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.takePicture(null,null,mPicture);
}
});
}
/**
* 카메라 사용여부 가능 체크
* @param context
* @return
*/
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.i(TAG, "Number of available camera : " + Camera.getNumberOfCameras());
return true;
} else {
Toast.makeText(context, "No camera found!", Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* 카메라 인스턴스 호출
* @return
*/
public Camera getCameraInstance(){
try{
// open() 의 매개변수로 int 값을 받을 수 도 있는데, 일반적으로 0이 후면 카메라, 1이 전면 카메라를 의미합니다.
mCamera = Camera.open();
}catch(Exception e){
Log.i(TAG,"Error : Using Camera");
e.printStackTrace();
}
return mCamera;
}
/** 이미지를 저장할 파일 객체를 생성
* 저장되면 Picture 폴더에 MyCameraApp 폴더안에 저장된다. (MyCameraApp 폴더명은 변경가능)
*/
private static File getOutputMediaFile(){
//SD 카드가 마운트 되어있는지 먼저 확인
// Environment.getExternalStorageState() 로 마운트 상태 확인 가능합니다
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyCameraApp");
// 없는 경로라면 따로 생성
if(!mediaStorageDir.exists()){
if(! mediaStorageDir.mkdirs()){
Log.d("MyCamera", "failed to create directory");
return null;
}
}
// 파일명을 적당히 생성, 여기선 시간으로 파일명 중복을 피한다
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timestamp + ".jpg");
Log.i("MyCamera", "Saved at"+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
System.out.println(mediaFile.getPath());
return mediaFile;
}
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// JPEG 이미지가 byte[] 형태로 들어옵니다.
File pictureFile = getOutputMediaFile();
if(pictureFile == null){
Toast.makeText(mContext, "Error camera image saving", Toast.LENGTH_SHORT).show();
return;
}
try{
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
//Thread.sleep(500);
mCamera.startPreview();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
};
}
처리 시간이 일정 시간 (제조사 마다 다를텐데 보통 5sec 라고 합니다)을 넘어서면 안드로이드 os 상에서 바로 ANR을 띄워 버리고 (어플리케이션 응답없음 팝업 ) 강제 종료 시켜버립니다. 사실 그런 이유가 아니더라도 사진 한장 찍고 다음 사진 찍을 준비를 하는데 시간이 오래 걸리는것도 좋은 설계는 아니니까 추가적인 처리를 넣게 된다면 스레드를 따로 생성 해야겠죠.
3. 마지막으로 매니페스트를 정리합니다,
<!-- 카메라 스토리지(저장) 사용여부를 등록합니다 -->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity android:name="MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
이정도만 하면 되더군요. 저도 성공했으니 다른분들도 다들 성공하시리라 믿습니다! 그럼 즐프하세요~
'공부 > 프로그래밍' 카테고리의 다른 글
아두이노에서 메모리 부족으로 2대사용하고 서로 데이터 통신하기 (4) | 2015.10.06 |
---|---|
빅데이터와 현실 (0) | 2015.10.01 |
Mediaplayer 로 지정되어있는 벨소리 재생하기 (0) | 2015.09.22 |
컴퓨터 언어를 공부할때는.... (0) | 2015.08.28 |
java 와 nodejs의 비교 (0) | 2015.08.28 |
댓글