반응형

얼마 전 삼성개발자사이트에 들어가서 둘러보다가 아래와 같이 삼성 카메라 SDK 지원 중단이 된다는 공지를 보았습니다.

 

As of Dec. 1, 2019, the Camera SDK(v1.0.0 - v1.3.2) is deprecated on all devices. We reget any inconvenience.

그 아래에 몇 개월 전 배포된 SDK 버전이 처량하게 보입니다.

 

 

외부 개발자들을 참여시켜서 기존 Android 카메라 API에서 지원하지 않는 삼성 폰의 카메라 확장기능을 지원하고자 시작한 것 같은데 아쉽다는 생각이 들었습니다.

 

SDK가 별거 아니라고 생각하는 분도 계실 수 있지만, SDK 개발하고 검증하고 배포한 이후 유지 보수하는 데는 많은 시간이 걸립니다.

 

왜 지원을 중단했는지 나름대로 생각을 해보니 구글에서 발표한 CameraX와 연관이 있는 것 같습니다.

 

관련한 근거로 SDC(삼성 개발자 컨퍼런스)에 발표된 아래 영상이 19.11월 초에 올라왔습니다.

 

제목이 Introducing the CameraX with Samsung인데, 구글의 안드로이드 카메라 프레임워크쪽 PM이 등장합니다.

 

CameraX 기능도 삼성 카메라 SDK와 중복되는 것이 많습니다.

 

향후 2개가 동시에 존재하는 것이 불가능한 것이 "카메라 확장 기능"을 위해서 삼성 카메라 SDK보다 여러 단말 벤더들도 지원할 수 있는 CameraX를 개발자들이 선호하게 될 것이기에 어쩔 수 없는 선택이었다고 생각됩니다.

 

삼성 입장에서는 아쉬울 수도 있지만, 뻔한 결과가 보이는 것을 계속 붙잡고 있을 수는 없는 거니깐요.

 

구글 입장에서 삼성은 아주 고마운 존재일 것 입니다.

 

새로운 기술이나 하드웨어에 대해서 삼성이 앞장서서 나가주고 검증을 해주니깐요.

 

일례로 지금은 당연히 카메라가 2개 이상 지원하지만 전에는 안드로이드 초기에는 Camera API에서 1개의 카메라만 지원했습니다.

 

(제가 기억하기로는) 삼성이 먼저 피처폰에서처럼 전면 카메라를 넣어주었죠. 이후 구글은 Camera API에 다수 카메라 지원하도록 적용하였고요.

 

작년 갤럭시 폴드 개발 시에는 폴더블 API 지원이 되도록 구글에서 많은 공조가 있었을 것입니다.

 

이번 글은 여기까지입니다. 읽어주셔서 감사합니다. 

 

반응형

Camera API를 어떻게 사용하는지 궁금할 때 참조할 수 있는 좋은 방법을 알려 드리겠습니다.

 

안드로이드 단말이 GMS를 탑재하고 구글의 승인을 받으려면 구글의 요구하는 조건을 만족시켜줘야 합니다.

 

기본적으로 CDD부터 프레임워크 호환성 확인을 위한 CTS 등 여러 테스트를 거치게 됩니다.

 

이중 CTS는 안드로이드 플랫폼에서 프레임워크 동작의 신뢰성을 위한 각종 API를 테스트합니다.

 

이 테스트 코드는 구글에서 작성하였고 소스가 오픈되어 있습니다.

 

예를 들어보겠습니다.

 

Camera API 중에서 setFocusMode를 어떻게 사용하는지 궁금하면 구글 검색으로 stackoverflow.com에 참고할 수 있는 대부분의 답변이 나옵니다.

 

아래의 경우는 setFocusMode가 동작하지 않는다는 문의와 답변입니다.

(관련 링크: https://stackoverflow.com/questions/11623266/camera-parameters-setfocusmode-is-not-working)

  

 

 

 

 

간결하고 문제 해결을 위한 무난한 답변입니다.

 

위의 문제는 지원하지 않는 Camera Focus mode를 사용하려고 해서 발생하는 문제였습니다.

 

답변에서는 지원 여부를 체크(getSupportedFocusModes)해서 지원하면 설정(setFocusMode)하게 처리되어 있습니다.

 

그럼, CTS 쪽에서는 어떻게 사용하고 있는지 볼까요?

 

여기에서도 체크(getSupportedFocusModes)후 지원하면 설정(setFocusMode)하게 처리되어 있습니다.

 

 

stackoverflow나 cts 모두 setFocusMode 사용하는 방법에 대해서 가리키는 것은 argument인 focus mode가 지원하는지 getSupportedFocusModes로 체크해서 지원하면 사용하도록 처리해야 한다는 것을 알 수 있습니다.

 

CTS 쪽 검색은 구글에서 아래와 같이 해주면 됩니다. (검색창에 "API명 site:android.googlesource.com/platform/cts")

 

 

CTS에 있는 모든 코드가 무결하다고 할 수는 없지만, 아무래도 구글에서 작성한 코드이기에 일정 수준이 상의 신뢰성이 보장된다고 볼 수 있습니다. stackoverflow.com에 나와 있지 않거나 거기 있는 코드가 신뢰 되지 않는다면 CTS에서 코드를 찾아보실 것을 권장해 드립니다.

 

이번 글은 여기까지입니다. 도움이 되셨기를 바랍니다.

반응형

안녕하세요. Simple& Happy Dev입니다.

앞선 글에서 손전등 앱을 개발하기 위해 Camera2 API를 이용해서 카메라 플래시를 제어하는 방법을 적어보았는데, 쉽지 않으셨을 겁니다.
손전등 앱으로는 잘 사용하지 않는 방법이니 Camera2에 대해서 공부했다고 위안으로 삼으셔도 됩니다.

이번에 알려드리는 방법은 이전 방법들 대비해서 매우 간단한  Flashlight API를 이용하는 방법입니다.

Android 6.0 이전까지 플래시는 카메라 디바이스의 보조 장치이고, 손전등에 대해서 고려되지 않았습니다.
그로 인하여 Camera/Camera2 API로 구현 시 비효율적인 부분이 많이 있었습니다.

Android 6.0(Marshmello, API level 23)부터는 손전등을 고려해서 Flashlight API가 추가되었고, 또한 안드로이드 시스템 UI에서 손전등이 자체 내장이 되기 시작했습니다.

아래와 같이 퀵세팅(QuickSetting)에 가면 손전등 타일이 추가되어 있습니다.

Flashlight API는 매우 간단하면서 카메라 권한이 필요하지 않다는 장점이 있습니다. 반면에 Android 6.0 이상의 단말기에만 동작한다는 제약성도 있습니다.

[Flashlight API 이용한 손전등 앱]

Camera API와 Camera2 API를 이용한 손전등 앱에서는 AndroidManifest.xml에 카메라 권한(android.permission.CAMERA)이 명시되어 있지 않으면 Exception이 발생합니다.

Camera / Camera2 API의 openCamera()를 호출하면, 내부에서 permission check하는 루틴에서 에러를 리턴서 Exception을 발생하게 합니다.

특히, Camera2에서는 Annotation(@RequiresPermission)으로 인하여 빌드 전에 미리 오류에 대한 확인이 가능합니다. 

@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {

openCameraForUid(cameraId, callback, handler, USE_CALLING_UID);
}

그런데, Flashlight API의 경우 Camera2에 추가된 것이지만, 사전에 openCamera() 하지 않고 사용할 수 있습니다.
그 결과 카메라 권한이 없어도 된 것입니다.

Flashlight API(setTorchMode) 이용한 손전등 앱에서 플래시가 동작하는 과정은 아래와 같습니다.

준비작업: 플래시 존재 여부 체크 - CameraManager 객체의 인스턴스 생성

플래시 켜기: setTorchMode(cameraId, true)

플래시 끄기: setTorchMode(cameraId, false)

매우 간단하기에 바로 소스로 들어가 보겠습니다.

[소스 설명]

레이아웃 (activity_main.xml) 
 
화면 한가운데 별 모양 버튼을 하나 추가 후 이걸 누르면 플래시가 켜지도록 할 예정입니다. 그리고, 한 번 더 누르면 버튼이 토글되어서 꺼지도록 하겠습니다. 별 모양 버튼은 SDK에 내장된 리소스를 사용하시면 됩니다. (앞쪽에 있는 글들과 동일함)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageButton
android:id="@+id/ibFlashOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>


자바 (MainActivity.java)

onCreate에서는 기기에 플래시가 지원하지 않으면 메시지 출력하고 3초 후 종료하도록 처리(delayedFinish())하도록 만들어 줍니다.
또한 별 모양 버튼에 대한 클릭 리스너를 만들어서 클릭 시 플래시를 켜주거나 꺼주고(flashlight()), 플래시 켜짐 상태에 따른 별 모양 이미지(켜짐: 노란색 별, 꺼짐: 흰색 별)를 변경하도록 처리해줍니다. (여기까지는 앞에 있는 부분과 공통)

Flashlight API(setTorchMode)를 사용하기 위해서 CameraManager 객체의 인스턴스를 획득합니다.

package com.example.help.nopermissionflashlight;

import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
private ImageButton mImageButtonFlashOnOff;
private boolean mFlashOn;

private CameraManager mCameraManager;
private String mCameraId;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Toast.makeText(getApplicationContext(), "There is no camera flash.\n The app will finish!", Toast.LENGTH_LONG).show();

delayedFinish();
return;
}

mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);

mImageButtonFlashOnOff = findViewById(R.id.ibFlashOnOff);
mImageButtonFlashOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flashlight();
mImageButtonFlashOnOff.setImageResource(mFlashOn ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
}
});
}

private void delayedFinish() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3500);
}

버튼을 눌러 flashlight()가 호출되면, CameraManager를 통해 카메라 id들을 가져온 후 필요한 카메라 특성을 구하고(getCameraCharacteristics), 원하는 조건(플래시 사용이 가능 & 화면 기준으로 뒤쪽에 있는 카메라)에 해당하는 카메라 id(주로 "0")를 구합니다.

이후 플래시 상태를 반전하기 위해서 mFlashOn을 토글시켜주고, setTorch()에 위에서 구한 카메라 id와 mFlashOn을 argument로 넘겨줍니다. setTroch()가 호출이 되면 mFlashOn 상태에 따라서 플래시가 켜지거나 꺼지게 됩니다.

void flashlight() {
if (mCameraId == null) {
try {
for (String id : mCameraManager.getCameraIdList()) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (flashAvailable != null && flashAvailable
&& lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
mCameraId = id;
break;
}
}
} catch (CameraAccessException e) {
mCameraId = null;
e.printStackTrace();
return;
}
}

mFlashOn = !mFlashOn;

try {
mCameraManager.setTorchMode(mCameraId, mFlashOn);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

프로젝트 압축화일
NoPermissionFlashlight.zip


앱 동작 영상

 


기타정보

Flashlight API를 이용하게 되면 Flashlight를 독점적으로 소유할 수 없습니다.
이것의 의미는 Flashlight API를 사용하는 다른 앱에서 똑같이 Flashlight API를 호출하게 되면 그쪽으로 소유가 넘어간다는 의미입니다.

아래 테스트 영상과 함께 설명드리겠습니다. 영상에는 세 가지 손전등앱이 있습니다.
1. 시스템 UI인 QuickSetting의 손전등
2. Camera API로 만든 PermissionFlashlight
3. Flashlight API로 만든 NoPermissionFlashlight

[case 1]
PermissionFlashlight 앱에서 Flash를 켜고, QuickSetting의 손전등을 확인하면 아이콘이 On이지만 Dimming 된 상태입니다.
이것의 의미는 QuickSetting의 손전등에서 Flashlight를 소유해서 제어할 수 없다는 의미입니다.
그리고, QuickSetting에서 Dimming 된 손전등의 On 아이콘을 누르면, "카메라 앱에서 카메라 플래시를 사용 중이어서 손전등을 켤 수 없습니다."라는 토스트가 나옵니다. 이는 QuickSetting의 손전등에서 PermissionFlashlight 앱이 Camera API로 만들어져서 카메라 앱으로 간주하고 있습니다.

[case 2]
NoPermissionFlashlight 앱에서 Flash를 켜고, QuickSetting의 손전등을 확인하면 아이콘이 On 되어 있습니다.
이는 QuickSetting의 손전등에서 다른 앱에서 Flashlight API를 사용해서 On 시킨 것을 인지한 상태입니다.
그리고, QuickSetting에서 손전등의 On 아이콘을 누르면 Off 상태로 아이콘이 변경되면서 플래시가 꺼집니다.
이는 QuickSetting의 손전등으로 Flashlight 소유가 넘어가서 제어되고 있다는 것을 의미합니다.

그런데, NoPermissionFlashlight 앱으로 돌아오면 가운데 별 모양은 계속 On 상태로 남아있죠?
이건 왜 그럴까요? 정답은 NoPermisionFlashlight 앱에서는 다른 앱에서 Flashlight API를 사용해서 On 시킨 것을 인지하는 리스너가 구현되어 있지 않기 때문입니다. QuickSetting의 손전등처럼 리스너를 구현해주면 플래시가 꺼지면 콜백되어서 Off 상태 인지가 가능해서 아이콘 모양 변경이 가능합니다. 


이상으로 "손전등 앱 만들기 세 번째 - Flashlight API 이용방법"에 대해서 설명해 드렸습니다.
상황에 따라서 세 가지 방법을 적절히 이용하시면 손전등 앱을 구현하실 수 있을 겁니다.

조금이나마 도움이 되셨으면 아래 공감 버튼을 눌러주세요.
(If this article helps you, please press the button below.)

반응형

안녕하세요. Simple& Happy Dev입니다.

이번에는 Camera2 API 이용해서 손전등 앱을 만들어 보도록 하겠습니다.
앞선 글에서 Camera2 API 이용하는 방법은 다른 방법들 대비해서 장점이 없기 때문에 일반적으로 사용하지 않는다고 말씀드렸습니다.

그래도, 궁금해하실 분이나 필요하실 분도 계실 것 같아서 글을 작성해봅니다.

[Camera2 API 이용한 손전등 앱]

안드로이드 초기 버전부터 사용되던 Camera API는 Android 5.0(API Level: 21)이 되어서야 Camera2 API로 Camera Framework이 변경되면서 구조적으로 큰 변화가 생기게 됩니다. 그 이전까지는 Camera API에서 기본 Feature(전면 카메라 지원, 얼굴인식 지원, 측광/초점 영역 지원, CAF 지원, AE/AWB Lock 지원 등) 추가하는 데 집중되었습니다.
(혹, 오해하실까 봐 말씀드리면, 위의 Feature들은 그 당시 단말기 업체의 카메라 앱에서는 추가되기 전부터 이미 있던 기능들입니다. 다만, 안드로이드 SDK에 API가 없어서 일반 앱 개발자들이 해당 기능들이 들어간 앱을 만들 수가 없었던 것이죠. 안드로이드 SDK에서 API로 지원하지 않는데, 단말기 업체에서는 어떻게 기능을 넣을 수 있게 된 건지에 대해서는 나중에 별도로 다룰 예정입니다)

Camera2 API로 넘어오면서 Camera Framework의 큰 구조적 변화는 성능 개선과 새로운 Feature 구현이 목적이었습니다.
플래시의 경우 이런 변화에 크게 영향은 없지만, 플래시를 제어하기 위한 접근이 조금 더 복잡해졌습니다.

아래는 여기에서 다루고 있는 예제로 만든 앱의 Sequence Diagram입니다. 이거 만드는 데 시간이 오래 걸렸습니다. :)

이후에 소스를 볼 때 아래 그림을 참조하면서 따라오시면 됩니다.

원본이미지 다운받기
FlashlightAppUsingCamera2API_sequence_diagram.zip

 

[소스 설명]

레이아웃 (activity_main.xml) 

화면 한가운데 별 모양 버튼을 하나 추가합니다. 별 모양 버튼은 SDK에 내장된 리소스를 사용하면 됩니다.
향후 이 버튼을 누르면 노란색 별모양으로 변하면서 플래시가 켜지게 할 예정입니다.
노란색 별모양은 자바 소스에서 버튼 클릭되면 변경되도록 처리할 것입니다. (이전 글의 레이아웃과 같습니다)

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageButton
android:id="@+id/ibFlashOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>


자바 (MainActivity.java)

onCreate에서는 기기에 플래시가 지원하지 않으면 메시지 출력하고 3초 후 종료하도록 처리(delayedFinish())하도록 만들어 줍니다.
또한 별 모양 버튼에 대한 클릭 리스너를 만들어서 클릭 시 플래시를 켜주거나 꺼주고(flashlight()), 플래시 켜짐 상태에 따른 별 모양 이미지를 변경하도록 처리해줍니다. (여기까지는 이전 글의 소스와 같습니다)

카메라 특성 정보를 얻고, 카메라 연결을 위해서는 먼저 CameraManager 객체의 인스턴스(mCameraManager)를 얻어야 합니다.

그 아래에 이미지 버튼의 클릭 리스너와 클릭시의 플래시 동작 및 이미지 버튼의 모양 변경에 대한 처리가 있습니다.
(이 부분은 뒷부분에 따로 빼내어서 설명합니다.) 


 

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Toast.makeText(getApplicationContext(), "There is no camera flash.\n The app will finish!", Toast.LENGTH_LONG).show();
delayedFinish();
return;
}

mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);

mImageButtonFlashOnOff = findViewById(R.id.ibFlashOnOff);
mImageButtonFlashOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flashlight();
mImageButtonFlashOnOff.setImageResource(mFlashOn ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
}
});
}

 

onResume의 openCamera()에서 CameraManager를 통해 카메라 id들을 가져온 후 필요한 카메라 특성을 구하고(getCameraCharacteristics), 원하는 정보(여기서는 플래시 사용이 가능하고, 화면 기준으로 뒤쪽에 있는 카메라) 조건에 해당하는 카메라 id(주로 "0")를 구합니다.
 
 

  

@Override
protected void onResume() {
super.onResume();
openCamera();
}


private void openCamera() {
try {
for (String id : mCameraManager.getCameraIdList()) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (flashAvailable != null && flashAvailable
&& lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
mCameraId = id;
break;
}
}

...

}

 

다음으로 CameraManager 객체의 openCamera()에 대한 설명입니다.
이것은 Camera API에서 Camera 객체의 open()에 해당합니다. 즉, 카메라 연결하는 것입니다.

openCamera()의 argument를 보시면, 첫 번째는 카메라 id로 앞서 구한 id를 넣어줍니다.
두 번째는 카메라 장치(CameraDevice)의 상태가 변경되면 콜백되는 CameraDevice.StateCallback 객체의 인스턴스를 넣어줍니다.
마지막은 핸들러인데, 여기서는 사용하지 않을 것입니다.

CameraDevice.StateCallback 콜백은 4가지 상태에 대해서 알려줍니다.
그중 아래 3가지는 반드시 구현해서 넣어줘야 합니다.
 - onOpened: 카메라 연결이 완료될 경우
 - onDisconnected: 카메라 장치를 더 이상 사용할 수 없을 경우
 - onError: 카메라 오류가 발생할 경우

onOpened이 콜백되면 parameter인 cameraDevice는 카메라 장치(CameraDevice) 객체의 인스턴스로 카메라 장치 연결종료에 사용이 필요하므로 mCameraDevice에 저장해둡니다. onOpened의 추가설명은 다음단계에서 계속합니다.
onDisconnected와 onError이 콜백될 경우 카메라 장치 연결을 종료합니다.

 

private void openCamera() {

...

mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;

... 

}

@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}

@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
Toast.makeText(getApplicationContext(), "A camera error has occurred. The app will finish!", Toast.LENGTH_LONG).show();
delayedFinish();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

 

다음으로 CaptureRequest를 만들어줘야 하는데, 이름만 보면 Capture 처리요청하는 객체로 생각될 수도 있지만,
이것은 Capture 뿐만 아니라 Preview, Recording, Video Snapshot, Manual control에 대한 처리요청을 위한 객체입니다.

CameraDevice의 CreateCaptureRequest()를 호출하면 argument로 넣은 템플릿 설정(여기서는  TEMPLATE_PREVIEW)으로 초기화되고, CaptureRequest.Builder 객체의 인스턴스를 구할 수 있습니다.

참고로 CaptureRequest.Builder는 CaptureRequest 객체를 포함한 빌더 클래스입니다.

사전에 CaptureRequest의 출력 Target을 설정(addTarget)해주는데, Target은 Surface가 됩니다.
플래시의 경우 Surface로 Preview가 되지 않아도 되기 때문에 Surface는 dummy surface로 만들어줍니다.
혹시 Surface 필요 없다 생각해서 출력 Target을 설정하지 않으면, Exception이 발생하게 됩니다.
최소한 한 개의 Target Surface가 있어야 합니다.

예제에서는 CreateCaptureRequest()를 호출하면 CaptureRequest.Builder 객체의 인스턴스를 mPreviewRequestBuilder에 저장하게 되어 있습니다. 


private void openCamera() {

...

mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;

try {
List<Surface> list = new ArrayList<>();
if (mTexture != null) {
mTexture.release();
}
mTexture = new SurfaceTexture(1);
Surface surface = new Surface(mTexture);
list.add(surface);
mPreviewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);

...

 

 

다음으로 CaptureSession을 만들어줘야합니다. CaptureSession은 전달받은 CaptureRequest들이 처리되는 곳입니다.
CameraDevice의 createCameraSession()를 호출후 콜백으로 CaptureSession의 인스턴스를 넘겨받을 수 있습니다.

creatureCamreaSeesion()의 argument를 보시면, 첫 번째는 Target Surface 리스트를 넣어줍니다.
이것은 앞 단계의 소스에 보면 dummy surface를 만들어서 리스트에 추가해주는 부분이 있습니다.
두 번째는 CaptureSession의 상태에 대한 업데이트가 되면 콜백되는 CameraCaptureSession.StateCallback 객체의 인스턴스를 넣어줍니다.
마지막은 핸들러인데, 여기서는 사용하지 않을 것입니다.

카메라 장치가 자체 구성을 완료하면 onConfigured가 호출됩니다. 이때 parameter로 cameraCaptureSession 객체의 인스턴스가 넘어옵니다. 이것은 다른곳에서도 사용이 필요하니, mCaptureSession에 저장해 둡니다.

마지막으로 CaptureRequest에 대한 처리요청을 CaptureSession에서 보내는 곳입니다.
이것을 수행 후 실제 동작이 이뤄지게 됩니다.
CameraCaptureSession의 setRepeatingRequest() 에 CaptureRequest를 argument로 넣어서 보내면 됩니다.
mPreviewRequestBuilder는 CaptureRequest.Builder의 인스턴스인데, build() 메서드를 이용하면 CaptureRequest를 가져올 수 있습니다.

setRepeatingRequest 이름처럼 CaptureRequest에 대한 처리를 반복하도록 합니다.
캡처의 경우 일회성이지만, 프리뷰의 경우 프레임이 계속 들어와야해서 setRepeatingRequest을 사용해야 하고 플래시도 사용자가 요청하기 전까지는 계속 켜져 있어야 하기 때문에 역시 setRepeatingRequest을 사용합니다.

Target Surface가 dummy surface라서 레이아웃으로 만든 화면에 어떠한 변화도 없지만, 만약 레이아웃에 Texture를 추가해주고 이것을 Surface로 만들어서 Target Surface로 추가해주면 카메라 프리뷰화면이 나올 것입니다.

여기까지 하면 플래시 제어를 위한 기본 작업이 완료됩니다.

 

private void openCamera() {

...

mCameraManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
                    ...
cameraDevice.createCaptureSession(list, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (null == mCameraDevice) {
return;
}
try {
mCaptureSession = cameraCaptureSession;
cameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
}, null);

}

...

}

 

이제부터 flashlight() 호출만으로 플래시가 작동합니다.

CaptureRequest.Builder의 set()은 Camera API의 Camera.Parameters의 set()이라고 생각하시면 쉽게 이해되실 겁니다.

그리고, mPreviewRequestBuilder.build()를 하면 플래시 세팅을 해준 CaptureRequest가 만들어져서 나오고, 이를 CameraCaptureSession에 실행하도록 전달하면 플래시가 동작하게 됩니다.
앞에서 설명했지만, setRepeatingRequest는 만든 CaptureRequest를 반복해서 CameraCaptureSession에 요청하기 때문에 사용자가 꺼주는 플래시 세팅을 다시 해주기 전까지는 계속 플래시가 켜져 있게 됩니다.

 

 

 

private void flashlight() {
mFlashOn = !mFlashOn;

mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mFlashOn ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);

try {
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

메니페스트(AndroidManifest.xml)

CameraManager의 openCamera() 실행시 카메라 권한을 요구하기 때문에 아래와 같이 <uses-permission android:name="android.permission.CAMERA" />를 메니페스트 파일에 넣어주면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.help.permissionflashlight">

<uses-permission android:name="android.permission.CAMERA" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

프로젝트 압축화일
PermissionFlashlight3.zip

 

앱 동작 영상


이상으로 "손전등 앱 만들기 두 번째 - Camera2 API 이용방법"에 대해서 설명해 드렸습니다.
Camera2 API를 처음 접하신 분들은 어려울 수도 있는 내용이었습니다.
다음번에는 간단하고 쉬운 방법인 Flashlight API를 이용한 손전등 앱을 만들어 보도록 하겠습니다.

조금이나마 도움이 되셨으면 아래 공감 버튼을 눌러주세요.
(If this article helps you, please press the button below.)

반응형

안녕하세요. Simple& Happy Dev입니다.

구글 플레이에는 수많은 손전등 / 플래시라이트(이하 손전등) 앱이 존재합니다.

일반적으로 스위치 버튼 하나 정도만 있으면 되는 비교적 간단한 UI에 카메라를 다뤄본 경험이 있다면 쉽게 만들 수 있기 때문일 겁니다. 여기서 카메라를 언급한 이유는 손전등의 불빛은 카메라 플래시(Camera Flash LED)를 사용하기에 그렇습니다.

손전등 앱을 만드는 방법은 몇 가지가 있습니다.
앞서 설명했듯이 카메라 플래시를 사용한다는 공통점이 있으나, 카메라 플래시 제어를 위해서 어떻게 접근하는지에 따라서 구분됩니다.

[손전등 앱의 카메라 플래시 제어하는 3가지 방법]

아래 그림에 보듯이 크게 카메라 권한을 가지고 (= 카메라 권한 필요) 플래시를 제어하는 방식과 카메라 권한 없이(카메라 권한 불필요) 플래시를 제어하는 방식으로 만들 수 있습니다.

카메라 플래시 제어 접근 방식

방법1 (Camera API 이용하는 방법)
 - 장점: 안드로이드 구버전도 지원 가능, 구현 용이
 - 단점: 카메라 권한 필요, deprecated API, 느린 속도(but, 약간 개선 가능 - 이글의 마지막에 다룰 예정)

방법2 (Camera2 API 이용하는 방법)
- 장점: 최신 Camera API
- 단점: 카메라 권한 필요, 구현 복잡, 안드로이드 5.0 (API Level 21) 이상부터 가능

방법3 (Flashlight API 이용하는 방법)
- 장점: 카메라 권한 불필요, 구현 매우 용이, 빠른 속도
- 단점: 안드로이드 6.0 (API Level 23) 이상부터 가능

Camera2 API 이용하는 방법은 거의 단점만 존재해서 일반적으로 사용하지 않는 방법입니다.
하지만, 궁금하시거나 해당 방법이 필요하신 분들이 있을 수도 있어서 다음 글에 다루도록 하겠습니다.

일단 이 글에서는 Camera API 이용해서 손전등 앱을 만드는 방법에 관해서 설명하도록 하겠습니다. 

[Camera API 이용한 손전등 앱]

애초에 플래시는 카메라를 보조해주기 위해서 들어간 디바이스입니다.
그래서, 하드웨어적인 연결이나 소프트웨어적인 접근이 카메라에 가깝게 되어 있습니다.

그 결과 플래시를 제어하기 위해서는 카메라에 접근한 이후 카메라 파라미터로 플래시를 넘겨줘야 했습니다.

플래시가 동작하는 순서는 다음과 같습니다.

플래시 켜기: 카메라 객체의 인스턴스 생성 및 연결(open)
                    - 카메라 파라미터에 플래시 모드를 Torch로 설정(setFlashMode)
                    - 카메라 프리뷰 시작(startPreview)

플래시 끄기 방법1: 카메라 프리뷰 종료 (stopPreview) - 카메라 연결 해제 및 릴리즈(release)

플래시 끄기 방법2: 카메라 파라미터에 플래시 모드를 Off로 설정(setFlashMode)
                             (중요 - 추후 카메라 프리뷰 종료 및 카메라 인스턴스를 릴리즈하는 코드가 존재해야 합니다.)

동작하는 순서에 대한 설명은 여기까지 하고 이제 소스로 들어가 보겠습니다.
아래 소스는 플래시를 끌 때 위에 있는 "플래시 끄기 방법1"로 하게 되어 있습니다.
"플래시 끄기 방법2"는 글의 마지막 부분에 설명되어 있습니다.

[소스 설명]

레이아웃 (activity_main.xml)
 

화면 한가운데 별 모양 버튼을 하나 추가해서 이걸 누르면 플래시가 켜지도록 할 예정입니다. 그리고, 한 번 더 누르면 버튼이 토글되어서 꺼지도록 하겠습니다. 별 모양 버튼은 SDK에 내장된 리소스를 사용하시면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<ImageButton
android:id="@+id/ibFlashOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

자바 (MainActivity.java)

onCreate에서는 기기에 플래시가 지원하지 않으면 메시지 출력하고 3초 후 종료하도록 처리(delayedFinish())하도록 만들어 줍니다.
또한 별 모양 버튼에 대한 클릭 리스너를 만들어서 클릭 시 플래시를 켜주거나 꺼주고(flashlight()), 플래시 켜짐 상태에 따른 별 모양 이미지(켜짐: 노란색 별, 꺼짐: 흰색 별)를 변경하도록 처리해줍니다.

package com.example.help.permissionflashlight;

import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
private static Camera mCamera = null;
private ImageButton mImageButtonFlashOnOff;
private boolean mFlashOn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
Toast.makeText(getApplicationContext(), "There is no camera flash.\n The app will finish!", Toast.LENGTH_LONG).show();
delayedFinish();
return;
}

mImageButtonFlashOnOff = findViewById(R.id.ibFlashOnOff);
mImageButtonFlashOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
flashlight();
mImageButtonFlashOnOff.setImageResource(mFlashOn ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off);
}
});
}

private void delayedFinish() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 3000);
}

초기에는 플래시가 꺼진 상태이고, 카메라 인스턴스인 mCamera가 null이므로 onCreate에 있던 flashlight()는 openCamera()에서 카메라 객체의 인스턴스를 생성 및 연결하고 문제가 없으면 true로 리턴하고, 이후 mFlashOn를 토글시켜주어서 켜진 상태(mFlashOn => true)로 세팅됩니다.

그리고, 플래시를 Torch 모드로 파라미터 세팅을 해주고, 프리뷰를 시작해주면 플래시가 켜지게 됩니다.

반대로 플래시가 켜진 상태에서 openCamera()에 진입하면 이전에 mCamera가 생성된 상태라서 별다른 처리 없이 true로 리턴되고 mFlashOn이 토글되면서 꺼진 상태(mFlashOn => false)로 세팅됩니다.

꺼진 상태일 경우 프리뷰를 종료하도록 하고, 카메라 인스턴스를 릴리즈해줍니다. 이후 mCamera는 꼭 null로 만들어주어야 이후 오동작을 하지 않게 됩니다.

private boolean openCamera() {
if (mCamera == null) {
try {
mCamera = Camera.open();
} catch (RuntimeException e) {
Toast.makeText(getApplicationContext(), "Camera open failed", Toast.LENGTH_LONG).show();
e.printStackTrace();
return false;
}
}
return true;
}

private void flashlight() {
if (openCamera()) {
mFlashOn = !mFlashOn;

if (mFlashOn) {
Camera.Parameters params = mCamera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);

mCamera.setParameters(params);
mCamera.startPreview();

} else {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
} else {
delayedFinish();
}
}
}

여기까지 작업 후 실행을 하게 되면 아래와 같은 에러를 만나게 되고 openCamera()의 try ~ catch 에 의해 강제 종료(Force Close)가 되지 않고, 메시지 출력 후 openCamera() 리턴을 false로 해서 3초 후 종료할 수 있게 되어 있습니다. 

W 3858     3858     ServiceManager:    Permission failure: android.permission.CAMERA from uid=10210 pid=3165
E  3858     3858     CameraService:      Permission Denial: cant use the camera pid=3165, uid=10210
W 3165     3165     CameraBase:         An error occurred while connecting to camera 0: Service not available
W 3165     3165     System.err:             java.lang.RuntimeException: Fail to connect to camera service
W 3165     3165     System.err:             at android.hardware.Camera.<init>(Camera.java:519)
W 3165     3165     System.err:             at android.hardware.Camera.open(Camera.java:379)
W 3165     3165     System.err:             at com.example.help.permissionflashlight.MainActivity.openCamera(MainActivity.java:50)
W 3165     3165     System.err:             at com.example.help.permissionflashlight.MainActivity.flashlight(MainActivity.java:61)
W 3165     3165     System.err:             at com.example.help.permissionflashlight.MainActivity$1.onClick(MainActivity.java:32)

위와 같은 현상이 일어난 것은 에러 메시지의 내용과 같이 카메라 권한이 없는 상태에서 접근했기 때문에 발생한 것입니다.


메니페스트(AndroidManifest.xml)

카메라 권한을 추가하기 위해서는 아래와 같이 <uses-permission android:name="android.permission.CAMERA" />를 메니페스트 파일에 넣어주면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.help.permissionflashlight">

<uses-permission android:name="android.permission.CAMERA" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

프로젝트 압축화일
PermissionFlashlight.zip


실행 결과


cf) 플래시 끄기 방법2

플래시 끄기 1번의 방법은 stopPreview 및 release도 해주기 때문에 플래시가 꺼질 때 시간이 좀 걸립니다.
개선된 방법인 플래시 끄기 방법2에서는 카메라 플래시 모드를 FLASH_MODE_OFF로 파라미터 설정해주기만 하면 되기에 속도가 빠릅니다.

그 외에 몇 군데 다르게 처리해야 하는 부분들도 있습니다.

앞서 간단히 언급한 부분이기도 한데, 플래시 끄는 곳에서는 카메라 파라미터에 플래시 모드를 off 해주는 것 외에는 다른 처리를 하지 않고 있어서 카메라 프리뷰 종료 및 카메라 인스턴스를 릴리즈하는 코드는 onPause 쪽에 넣어주어야 합니다.

그리고, 그 코드가 onPause로 이동했기에 객체의 인스턴스 생성하는 코드는 onResume에 넣어주는 게 적당합니다.

자세한 코드는 소스를 첨부하니 참고하시길 바랍니다.
PermissionFlashlight2.zip


이상으로 "손전등 앱 만들기 첫 번째 - 개요 및 Camera API 이용방법"에 대해서 설명해 드렸습니다.
다음번에는 Camera2 API 이용해서 손전등 앱을 만들어 보도록 하겠습니다.

조금이나마 도움이 되셨으면 아래 공감 버튼을 눌러주세요.
(If this article helps you, please press the button below.)

반응형

특정 디렉토리의 미디어 화일(이미지, 동영상, 소리)을 삭제하지 않고 앱(갤러리, 비디오, 음악)에서 보이지 않도록 하는 방법

 

1. 마켓에서 앱을 설치했는데, 그 앱의 데이터중에 미디어 화일(이미지, 동영상, 소리)이 앱에 보이는 경우

2. 개발한 앱의 미디어 화일들이 다른 앱에서 보이는 경우

 

사실 1, 2 는 같은 내용입니다.

1의 경우 주로 소비자(고객)의 고민이고, 2의 경우 초보 개발자의 고민일 것입니다.

 

결론부터 말하면, 미디어 화일이 있는 디렉토리에 .nomedia 화일 하나만 만들어주면 됩니다.

 

.nomedia 화일을 추가한 이후 부터는 굳이 삭제하지 않아도 앱에서 보이지 않게 됩니다.

 

앱에서 사용하는 미디어 화일이 노출이 된다는 것은 해당 앱 개발시 실수라고 볼 수 있습니다.

 

혹 소비자가 해당 미디어 화일을 삭제한 경우에 앱에서 오동작이 있을 수 있기 때문입니다.

 

다른 관점에서 보면, 특정 디렉토리에 있는 미디어 화일들을 삭제하지 않고 단지 보이지 않도록 하기위한 방법으로 이용할 수도 있습니다.

 

.nomedia 화일 만드는 것에 어려움을 느낀다면 마켓에 앱을 다운받아서 대신 할 수도 있습니다.

 

StudioKUMA .nomedia Manager 라는 무료앱이면서 광고도 없는 앱을 추천드립니다.

 

 

 

 

사용방법은 아래에 적혀있는 것처럼 디렉토리를 길게 누르면 "Disable Media Scanning" 나오는데, 이것을 선택하면 해당 디렉토리에 있는 미디어 화일들은 앱에서 보이지 않게 되비다.

똑같이 한번 더 수행하면, "Enable Media Scanning" 나오면서 앱에서 미디어 화일들이 보이도록 할수  있습니다.

Android 4.3+ 이후부터는 Refresh (위치: 더보기 - Refresh) 를 해줘야 합니다.

 

 

감사합니다.

+ Recent posts