스마트폰을 사용하다보면 스마트폰의 센서를 사용한다.


그중에서 방향 계산을 하는 일이 있을 것이다.


일반적으로 아래 그림처럼 Azimuth, Pitch, Roll을 이용하여 방향을 계산한다.


여기서 Azimuth는 항상 Y+의 방향과 북쪽 방향의 각도 차이를 나타낸다.



지도 앱처럼 북쪽이 Y+인 경우는 큰 문제가 없다. 하지만 카메라가 가르키는 방향을 북으로 잡으려면 어떻게 할까


그에 따라서 방향각을 다시 계산해야하는 경우가 있다.


예제는 책(안드로이드 프로그래밍 / 한빛미디어 / 김상형 지음)의 내용을 응용하여 만들었다.


SensorEventListener mSeonsorListneer = new SensorEventListener() {
float[] mR = new float[9];
float[] mI = new float[9];
float[] mV = new float[9];
@SuppressLint("SetTextI18n")
@Override
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mGravity = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mGeometric = event.values.clone();
break;
}

if(mOrientCount++ % FREQ != 0) return;

if(mGravity != null && mGeometric != null) {
SensorManager.getRotationMatrix(mR, mI, mGravity, mGeometric);
float inclination = SensorManager.getInclination(mI);
SensorManager.getOrientation(mR, mV);

mTxtOrient.setText(
("회수 = " + mOrientCount / FREQ + "회\n")
+ "\nGra : " + dumpValues(mGravity)
+ "\nMag : " + dumpValues(mGeometric)
+ "\n\nR : \n" + dumpMatrix(mR)
+ "\nI : \n" + dumpMatrix(mI)
+ "\ninclination : " + inclination
+ "\n\nRot : \n" + dumpMatrix(mV)
+ "\n\nTop : "
+ "\nx : " + String.format("%.3f", Math.cos(mV[0])*Math.cos(mV[1]))
+ "\ny : " + String.format("%.3f", Math.sin(mV[0])*Math.cos(mV[1]))
+ "\nz : " + String.format("%.3f", -Math.cos(mV[1]-Math.PI/2))

+ "\n\nLeft : "
+ "\nx : " + String.format("%.3f", -Math.cos(mV[0])*Math.sin(mV[1])*Math.sin(mV[2]) + Math.sin(mV[0])*Math.cos(mV[2]))
+ "\ny : " + String.format("%.3f", -Math.sin(mV[0])*Math.sin(mV[1])*Math.sin(mV[2]) - Math.cos(mV[0])*Math.cos(mV[2]))
+ "\nz : " + String.format("%.3f", Math.cos(mV[1])*Math.sin(mV[2]))

+ "\n\nBack : "
+ "\nx : " + String.format("%.3f", -Math.cos(mV[0])*Math.sin(mV[1])*Math.cos(mV[2]) + Math.sin(mV[0])*Math.sin(mV[2]))
+ "\ny : " + String.format("%.3f", -Math.sin(mV[0])*Math.sin(mV[1])*Math.cos(mV[2]) - Math.cos(mV[0])*Math.sin(mV[2]))
+ "\nz : " + String.format("%.3f", Math.cos(mV[1])*Math.sin(mV[2]-Math.PI/2))
);
}
}

다른 부분은 레이아웃 설정 관련 설정이므로 생략한다. 센서리스너만 보도록 한다.


센서의 사용법은 다른 블로그에도 많으니 생략한다.


mV는 센서의 azimuth, pitch, roll값을 저장한다. 이 값을 이용하여 아래는 벡터를 이용하였다.


수식은 아래 링크를 참고하면 된다.


http://stackoverflow.com/questions/1568568/how-to-convert-euler-angles-to-directional-vector


Top은 Y+방향의 벡터


Left는 X-방향의 벡터


Back은 Z-방향의 벡터이다.


실질적으로 만들고 싶었던 기능은 카메라가 어느 방향을 가르키는지 알려주는 코드를 만들고 싶었다.


그럼 어떻게 만들었는지 보도록 하자.


수학적으로 회전행렬 * 벡터를 사용하였다.


회전행렬은 다음과 같다.


| cos(yaw)cos(pitch) -cos(yaw)sin(pitch)sin(roll)-sin(yaw)cos(roll) -cos(yaw)sin(pitch)cos(roll)+sin(yaw)sin(roll)|
| sin(yaw)cos(pitch) -sin(yaw)sin(pitch)sin(roll)+cos(yaw)cos(roll) -sin(yaw)sin(pitch)cos(roll)-cos(yaw)sin(roll)|
| sin(pitch)          cos(pitch)sin(roll)                            cos(pitch)sin(roll)|


여기에 [1, 0, 0]을 곱하면 Top(Y+)방향의 벡터


여기에 [0, 1, 0]을 곱하면 Left(X-)방향의 벡터


여기에 [0, 0, 1]을 곱하면 Back(Z-)방향의 벡터

 


+ "\n\nTop : "
+ "\nx : " + String.format("%.3f", Math.cos(mV[0])*Math.cos(mV[1]))
+ "\ny : " + String.format("%.3f", Math.sin(mV[0])*Math.cos(mV[1]))
+ "\nz : " + String.format("%.3f", -Math.cos(mV[1]-Math.PI/2))



우선, Top(Y+)방향을 보자


여기서 주의할 점은 pitch의 범위이다. 센서매너저에서 출력된는 범위는 0 ~ 2*PI이다. 하지만 실질적으로 필요한 z의 값은 -1 ~ 1이다.


따라서 z의 값을 -PI ~ PI로 변경하기 위해서 코사인 함수내에 PI/2를 빼주었다. 


                    + "\n\nBack : "
+ "\nx : " + String.format("%.3f", -Math.cos(mV[0])*Math.sin(mV[1])*Math.cos(mV[2]) + Math.sin(mV[0])*Math.sin(mV[2]))
+ "\ny : " + String.format("%.3f", -Math.sin(mV[0])*Math.sin(mV[1])*Math.cos(mV[2]) - Math.cos(mV[0])*Math.sin(mV[2]))
+ "\nz : " + String.format("%.3f", Math.cos(mV[1])*Math.sin(mV[2]-Math.PI/2))


이와 마찬가지로 Back(Z-)의 벡터는 roll값에 PI/2의 값을 빼주었다. 


이렇게 하지 않는다면, z값이 1이 나와야할 경우에 0이 나오거나 항상 양수값만 나오는 경우가 발생한다.


코드를 대충 짰기 때문에 x, y, z가 순방향이 아닐것이다. 사용할거라면 직접 고치기 바란다.


GitHub : https://github.com/pchan1401-ICIL/GetOrientation.git



안드로이드 6.0으로 업그레이드 되면서 권한설정이 방식이 변경되었다.


기존에는 앱에서 요청하는 모든 권한을 허용해야 앱을 사용할 수 있다.


구글은 그러한 권한방식이 좋아하지 않았는지 바꿔버렸다.


------------------------------------------------------------------------------------------------------


public class MainActivity extends AppCompatActivity {
private Activity mainActivity = this;

private static final int REQUEST_MICROPHONE = 3;
private static final int REQUEST_EXTERNAL_STORAGE = 2;
private static final int REQUEST_CAMERA = 1;

private TextView resultText;

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

Button cameraBtn = (Button)findViewById(R.id.cameraBtn);
Button storageBtn = (Button)findViewById(R.id.storageBtn);
Button micBtn = (Button)findViewById(R.id.micBtn);

resultText = (TextView)findViewById(R.id.resultText);

cameraBtn.setOnClickListener(buttonClickListener);
storageBtn.setOnClickListener(buttonClickListener);
micBtn.setOnClickListener(buttonClickListener);
}

private View.OnClickListener buttonClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.cameraBtn:
int permissionCamera = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(mainActivity, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
} else {
resultText.setText("camera permission authorized");
}
break;
case R.id.storageBtn:
int permissionReadStorage = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE);
int permissionWriteStorage = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permissionReadStorage == PackageManager.PERMISSION_DENIED || permissionWriteStorage == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(mainActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
} else {
resultText.setText("read/write storage permission authorized");
}
break;
case R.id.micBtn:
int permissionAudio = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.RECORD_AUDIO);
if(permissionAudio == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(mainActivity, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_MICROPHONE);
} else {
resultText.setText("recording audio permission authorized");
}
break;
}
}
};

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CAMERA:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.CAMERA)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
resultText.setText("camera permission authorized");
} else {
resultText.setText("camera permission denied");
}
}
}
break;
case REQUEST_EXTERNAL_STORAGE:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
resultText.setText("read/write storage permission authorized");
} else {
resultText.setText("read/write storage permission denied");
}
}
}
break;
case REQUEST_MICROPHONE:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.RECORD_AUDIO)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
resultText.setText("recording audio permission authorized");
} else {
resultText.setText("recording audio permission denied");
}
}
}
break;
}
}
}


---------------------------------------------------------------------------------------------------------



카메라의 권한설정 메커니즘만 설명하겠다.


---------------------------------------------------------------------------------------------------------



int permissionCamera = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(mainActivity, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
} else {
resultText.setText("camera permission authorized");
}


---------------------------------------------------------------------------------------------------------



permissionCamera를 이용하여 권한을 확인한다.

권한이 있다면 else문으로 빠져나간다. 

반면에 권한이 없다면 권한을 요청한다.



---------------------------------------------------------------------------------------------------------


switch (requestCode) {
case REQUEST_CAMERA:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.CAMERA)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
resultText.setText("camera permission authorized");
} else {
resultText.setText("camera permission denied");
}
}
}
break;


---------------------------------------------------------------------------------------------------------


onRequestPermissionsResult는 사용자의 권한 선택을 한 내용들이 들어있다.


여기서 Camera의 권한이 있다면 camera permission authorized가 출력되고


권한이 없다면 camera permission denied가 출력된다.


예제 코드는


android permisstion : https://github.com/pchan1401-ICIL/AndroidPermission


더 좋은 설명은 아래 블로그를 참고하자


박상권님 블로그 : http://gun0912.tistory.com/55


안드로이드 5.0 이상의 버전은 Camera API 대신 Camera2 API를 사용할 수 있다. 


이 코드는 안드로이드 6.0에서도 사용이 가능하도록 하였다.

-------------------------------------------------------------------------------------------------------------------

public class MainActivity extends AppCompatActivity {
private TextureView mCameraTextureView;
private Preview mPreview;

Activity mainActivity = this;

private static final String TAG = "MAINACTIVITY";

static final int REQUEST_CAMERA = 1;

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

mCameraTextureView = (TextureView) findViewById(R.id.cameraTextureView);
mPreview = new Preview(this, mCameraTextureView);

}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CAMERA:
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int grantResult = grantResults[i];
if (permission.equals(Manifest.permission.CAMERA)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
mCameraTextureView = (TextureView) findViewById(R.id.cameraTextureView);
mPreview = new Preview(mainActivity, mCameraTextureView);
Log.d(TAG,"mPreview set");
} else {
Toast.makeText(this,"Should have camera permission to run", Toast.LENGTH_LONG).show();
finish();
}
}
}
break;
}
}

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

@Override
protected void onPause() {
super.onPause();
mPreview.onPause();
}
}

-----------------------------------------------------------------------------------------------------------------


위는 이코드의  MainActivity이다. 카메라의 화면을 보여주는 TextureView와 카메라의 기능을 하는 Preview를 선언하였다.


-----------------------------------------------------------------------------------------------------------------


public class Preview extends Thread {
private final static String TAG = "Preview : ";

private Size mPreviewSize;
private Context mContext;
private CameraDevice mCameraDevice;
private CaptureRequest.Builder mPreviewBuilder;
private CameraCaptureSession mPreviewSession;
private TextureView mTextureView;

public Preview(Context context, TextureView textureView) {
mContext = context;
mTextureView = textureView;
}

private String getBackFacingCameraId(CameraManager cManager) {
try {
for (final String cameraId : cManager.getCameraIdList()) {
CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cameraId);
int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING);
if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) return cameraId;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return null;
}

public void openCamera() {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "openCamera E");
try {
String cameraId = getBackFacingCameraId(manager);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];

int permissionCamera = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, MainActivity.REQUEST_CAMERA);
} else {
manager.openCamera(cameraId, mStateCallback, null);
}
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e(TAG, "openCamera X");
}

private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener(){

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
// TODO Auto-generated method stub
Log.e(TAG, "onSurfaceTextureAvailable, width="+width+",height="+height);
openCamera();
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
int width, int height) {
// TODO Auto-generated method stub
Log.e(TAG, "onSurfaceTextureSizeChanged");
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// TODO Auto-generated method stub
return false;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// TODO Auto-generated method stub
}
};

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override
public void onOpened(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onOpened");
mCameraDevice = camera;
startPreview();
}

@Override
public void onDisconnected(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onDisconnected");
}

@Override
public void onError(CameraDevice camera, int error) {
// TODO Auto-generated method stub
Log.e(TAG, "onError");
}

};

protected void startPreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
Log.e(TAG, "startPreview fail, return");
}

SurfaceTexture texture = mTextureView.getSurfaceTexture();
if(null == texture) {
Log.e(TAG,"texture is null, return");
return;
}

texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);

try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mPreviewBuilder.addTarget(surface);

try {
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

@Override
public void onConfigured(CameraCaptureSession session) {
// TODO Auto-generated method stub
mPreviewSession = session;
updatePreview();
}

@Override
public void onConfigureFailed(CameraCaptureSession session) {
// TODO Auto-generated method stub
Toast.makeText(mContext, "onConfigureFailed", Toast.LENGTH_LONG).show();
}
}, null);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

protected void updatePreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice) {
Log.e(TAG, "updatePreview error, return");
}

mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
Handler backgroundHandler = new Handler(thread.getLooper());

try {
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void setSurfaceTextureListener()
{
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}

public void onResume() {
Log.d(TAG, "onResume");
setSurfaceTextureListener();
}

private Semaphore mCameraOpenCloseLock = new Semaphore(1);

public void onPause() {
// TODO Auto-generated method stub
Log.d(TAG, "onPause");
try {
mCameraOpenCloseLock.acquire();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
Log.d(TAG, "CameraDevice Close");
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.");
} finally {
mCameraOpenCloseLock.release();
}
}
}


----------------------------------------------------------------------------------------------------------------------


Camera2 API는 기존의 Camera API와 다르게 선언해야하는 여러가지 Camera 클래스들이 있다.


CameraDevice는 카메라 기기를 나타내고 CameraManager는 카메라의 기능을 시키는 요소이다.


카메라를 켜기위해 openCamera를 사용하였는데


이 함수는 SurfaceTextureListener내부의 onSurfaceTextureAvailable에 위치한다


TextureView가 사용이 가능하다면 카메라를 켜는 기능이다.


----------------------------------------------------------------------------------------------------------------------


public void openCamera() {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "openCamera E");
try {
String cameraId = getBackFacingCameraId(manager);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];

int permissionCamera = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
if(permissionCamera == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, MainActivity.REQUEST_CAMERA);
} else {
manager.openCamera(cameraId, mStateCallback, null);
}
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e(TAG, "openCamera X");
}


----------------------------------------------------------------------------------------------------------------------


또한 카메라를 다시 켜고 싶다면, SurfaceTextureListener만 다시 선언해 주면 된다.


----------------------------------------------------------------------------------------------------------------------

public void setSurfaceTextureListener()
{
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}

----------------------------------------------------------------------------------------------------------------------


카메라가 켜진다면 카메라 상태 콜백함수가 실행된다.


----------------------------------------------------------------------------------------------------------------------

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

@Override
public void onOpened(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onOpened");
mCameraDevice = camera;
startPreview();
}

@Override
public void onDisconnected(CameraDevice camera) {
// TODO Auto-generated method stub
Log.e(TAG, "onDisconnected");
}

@Override
public void onError(CameraDevice camera, int error) {
// TODO Auto-generated method stub
Log.e(TAG, "onError");
}

};

----------------------------------------------------------------------------------------------------------------------


이 함수 내부에 onOpened함수가 있는데 카메라가 켜지면 자동적으로 실행된다. 


여기서 실질적으로 하는 함수는 startPreview함수이다.


----------------------------------------------------------------------------------------------------------------------


protected void startPreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
Log.e(TAG, "startPreview fail, return");
}

SurfaceTexture texture = mTextureView.getSurfaceTexture();
if(null == texture) {
Log.e(TAG,"texture is null, return");
return;
}

texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);

try {
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mPreviewBuilder.addTarget(surface);

try {
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

@Override
public void onConfigured(CameraCaptureSession session) {
// TODO Auto-generated method stub
mPreviewSession = session;
updatePreview();
}

@Override
public void onConfigureFailed(CameraCaptureSession session) {
// TODO Auto-generated method stub
Toast.makeText(mContext, "onConfigureFailed", Toast.LENGTH_LONG).show();
}
}, null);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

-----------------------------------------------------------------------------------------------------------------------


startPreview함수는 위와 같다. 여기서는 카메라의 출력을 어디로 보낼 것인지 설정한다.


출력은 최대 3개까지 지정할 수 있다.


여기서는 카메라 프리뷰만 설정하였는데, 사진촬영 동영상촬영은 이 함수 내에 코드를 몇개만 바꿔서 사용할 수 있다.


카메라의 출력은 surface로 저장되어 mPreviewBuilder에 들어가게 된다. 


-----------------------------------------------------------------------------------------------------------------------


protected void updatePreview() {
// TODO Auto-generated method stub
if(null == mCameraDevice) {
Log.e(TAG, "updatePreview error, return");
}

mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
Handler backgroundHandler = new Handler(thread.getLooper());

try {
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

-----------------------------------------------------------------------------------------------------------------------


마지막으로 updatePreview함수가 실행된다.


이 함수는 화면의 프리뷰를 계속 변경시켜주는 Thread를 실행시키는 함수이다.


이것으로 카메라의 프리뷰가 실행된다.



아래는 Camera2 API를 이해하기 위해 코드이다.


Camera2Raw : https://github.com/googlesamples/android-Camera2Raw


예제로 사용한 코드는 GitHub에 업로드 하였다.


mapp : https://github.com/pchan1401-ICIL/mapp









+ Recent posts