달력

2

« 2025/2 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
728x90
반응형

안드로이드 - Android에서 기본 카메라 앱 연동 개발자Tip

원본출처:http://helloworld.naver.com/helloworld/1819

NHN 기술전략팀 김상경

Android 기본(Built-in) 카메라 앱으로 사진을 촬영하면, Goggles와 연동해서 사진을 분석하는 기능이 Google Goggles v1.6에 추가(링크)되었습니다. Google이 아닌 Third-Party 개발자도 기본 카메라 앱에서 촬영한 사진을 획득하여 자신의 서비스에 접목시킬 수 있다면 좀 더 사용자 친화적인 서비스를 개발할 수 있지 않을까 하는 생각에서 주목되는 기능입니다.

이 포스팅에서는 Google Goggles v1.6이 어떻게 Android 기본 카메라 앱에서 촬영한 사진을 획득하는지(그림 1의 2번 과정) 분석해 봤습니다.

  • 기본 카메라 앱과 Goggles 연동 과정

    다음 그림은 기본 카메라 앱에서 촬영된 사진이 Goggles를 통해 분석되어 사용자에게 전달되는 과정을 나타낸 것이다.

    122811_0537_Android1.jpg

    그림 1 기본 카메라 앱과 Goggles v1.6 연동에 의한 사진 분석 과정

    기본 카메라 앱과 Goggles v1.6의 연동 순서는 다음과 같다.

  • 1. 기본 카메라 앱으로 사진을 촬영한다.
  • 2. Goggles 앱에서 기본 카메라 앱이 촬영한 사진을 획득한다.
  • 3. Goggles 앱이 획득한 사진을 Goggles Server에 전달하여 분석한다.
  • 4. Goggles Server에서 분석된 사진 결과를 NotificationBar로 통지한다.
    • 이때, 분석된 결과가 존재하는 경우에만 NotificationBar로 통지된다.
    • NotificationBar로 통지된 결과를 선택하면 사진 분석 결과를 확인할 수 있다.

    위와 같은 연동 과정 중 두 번째 항목의 기본 카메라 앱에서 촬영한 사진을 Goggles 앱이 획득하는 방법을 분석해 봤다. 인터넷과 Android DDMS 로그 분석을 사용해 다음과 같은 두 가지 연동 방법을 확인했다.

    • com.android.camera.NEW_PICTURE Intent를 이용한 방식
    • MediaStore Polling 방식

    이 두 가지 연동 방법을 자세히 살펴보자.

  • 연동 방법 1 com.android.camera.NEW_PICTURE Intent

    122811_0537_Android2.jpg

    그림 2 com.android.camera.NEW_PICTURE Intent Broadcasting

    기본 카메라 앱으로 사진을 촬영하면 다음과 같은 과정을 거친다.

  • 촬영된 사진을 MediaStore에 저장한다.
  • com.android.camera.NEW_PICTURE Intent Broadcasting: MediaStore에 저장된 사진의 URI를 Intent의 Data 영역에 첨부한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class CameraActivity extends Activity {
    public final static String ACTION_NEW_PICTURE = "com.android.camera.NEW_PICTURE";
    // ... 생략 ...
    /**
    * com.android.camera.NEW_PICTURE Intent Broadcasting
    *
    * @param uri MediaStore에 저장된 사진의 URI
    */
    private void broadcastIntent(Uri uri) {
    Intent intent = new Intent();
    intent.setAction(ACTION_NEW_PICTURE);
    intent.setData(uri);
    sendBroadcast(intent);
    }
    /**
    * MediaStore에 사진 저장하기
    *
    * @param dirPath 사진을 저장할 Directory 위치
    * @param filename 사진 파일 이름
    * @param jpegByteArray Jpeg 파일의 ByteArray
    * @return MediaStore에 저장된 사진의 URI
    */
    private Uri insertMediaStore(String dirPath, String filename, byte[] jpegByteArray) {
    String filePath = dirPath + "/" + filename;
    try {
    ContentValues values = new ContentValues();
    values.put(Images.Media.DATE_TAKEN, new Date().getTime());
    values.put(Images.Media.ORIENTATION, "0");
    String title = filename.replace(".jpg", "");
    values.put(Images.Media.TITLE, title);
    values.put(Images.Media.DISPLAY_NAME, filename);
    values.put(Images.Media.MIME_TYPE, "image/jpeg");
    values.put(Images.Media.SIZE, jpegByteArray.length);
    values.put("_data", filePath);
    Uri uri = getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
    OutputStream os = getContentResolver().openOutputStream(uri);
    os.write(jpegByteArray);
    os.close();
    Logger.info("MediaStore Inserted URI:" + uri.toString());
    return uri;
    } catch(Exception ex) {
    Logger.error(ex, "Failed to save the Bitmap file. FilePath: %s", filePath);
    Toast.makeText(this, "Bitmap 저장 실패", Toast.LENGTH_SHORT).show();
    }
    return null;
    }
    }

    코드 1 com.android.camera.NEW_PICTURE Intent Broadcasting 예제 코드

  • Goggles 앱은 브로드캐스팅된 Intent를 수신한 후 Intent에 첨부된 URI를 사용해서 MediaStore에 저장된 사진을 획득한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class IntentListenerService extends Service {
    // ... 생략 ...
    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
    Logger.info("Intent received - " + intent.toString());
    }
    };
    private void startBroadcastReceiver() {
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.android.camera.NEW_PICTURE");
    try {
    filter.addDataType("image/*");
    registerReceiver(broadcastReceiver, filter);
    } catch (MalformedMimeTypeException ex) {
    Logger.error(ex, "BroadcastReceiver registration failure.");
    }
    }
    private void stopBroadcastReceiver() {
    unregisterReceiver(broadcastReceiver);
    }
    }

    코드 2 com.android.camera.NEW_PICTURE Intent BroadcastReceiver 예제 코드

    기본 카메라 앱이 사용하는 com.android.camera.NEW_PICTURE Intent는 Android SDK에 등록된 표준 API가 아니다(Android SDK Reference에서 찾을 수 없다). 그러나 많은 기본 카메라 앱에서 사진 촬영 후 com.android.camera.NEW_PICTURE Intent를 브로드캐스팅하고 있다. 그러므로 표준 API는 아니지만 Defacto 표준 정도로 생각할 수 있다.

    com.android.camera.NEW_PICTURE Intent를 사용하여 연동하는 앱은 Goggles v1.6 외에 Picasa Uploader가 있다.

  • 연동 방법 2 MediaStore Polling

    연동 방법 1에서 설명한 com.android.camera.NEW_PICTURE Intent는 Android 표준 API가 아니다. 기본 카메라 앱이 com.android.camera.NEW_PICTURE Intent를 브로드캐스팅할지 여부는 Android 기본 카메라 앱을 만든 제조사가 결정한다.

    이를 보완하기 위해 Goggles v1.6은 MediaStore를 1분마다 Polling한다.

    122811_0537_Android3.jpg

    그림 3 MediaStore Polling

    Goggles는 다음 그림과 같이 백그라운드에서 실행된다.

    122811_0537_Android4.jpg

    그림 4 백그라운드 프로세스로 실행되고 있는 Goggles

    실제 Android DDMS 로그에 1분마다 기록되는 것을 확인할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    09-26 15:32:51.382: INFO/goggles(910): AuthenticatedService: Allowing authenticated operation for account testphone@nhn.com
    09-26 15:32:52.198: INFO/goggles(910): FreshnessHelper: Checking for images newer than 1317018153743
    09-26 15:33:52.206: INFO/goggles(910): AuthenticatedService: Allowing authenticated operation for account testphone@nhn.com
    09-26 15:33:52.593: INFO/goggles(910): FreshnessHelper: Checking for images newer than 1317018153743
    09-26 15:34:52.597: INFO/goggles(910): AuthenticatedService: Allowing authenticated operation for account testphone@nhn.com
    09-26 15:34:52.870: INFO/goggles(910): FreshnessHelper: Checking for images newer than 1317018153743
    09-26 15:35:52.874: INFO/goggles(910): AuthenticatedService: Allowing authenticated operation for account testphone@nhn.com
    09-26 15:35:53.152: INFO/goggles(910): FreshnessHelper: Checking for images newer than 1317018153743
    09-26 15:36:53.155: INFO/goggles(910): AuthenticatedService: Allowing authenticated operation for account testphone@nhn.com
    09-26 15:36:53.663: INFO/goggles(910): FreshnessHelper: Checking for images newer than 1317018153743
    09-26 15:36:53.663: INFO/goggles(910): PictureRequestService: Creating a Picture from Image
    [imageUri=content://media/external/images/media/1431, description=null, dateTaken=1317018985211, orientation=0]
    09-26 15:36:54.105: WARN/goggles(910): TraceTracker: Null action in getTracingCookieForAction.
    09-26 15:36:54.112: ERROR/goggles(910): TraceTracker: [REQUEST_TO_RESPONSE]: null TraceAction in beginInterval!
    09-26 15:36:57.042: WARN/goggles(910): TraceTracker: TraceAction 0 has already been completed, cannot end interval REQUEST_TO_RESPONSE
    09-26 15:36:57.042: WARN/goggles(910): TraceTracker: No request found for action number 0
    09-26 15:36:58.378: INFO/goggles(910): QueueingQueryExecutor: Background query succeeded; checking for another query to run.
    09-26 15:36:58.452: INFO/goggles(910): NotificationHelper: Launching notification: Notification(vibrate=default,sound=default,defaults=0xffffffff,flags=0x18)

    코드 3 Android DDMS에 기록된 Goggles의 1분 간격 Polling 로그

    뿐만 아니라 사진을 찍어 MediaStore를 통해 /mnt/sdcard/DCIM 폴더에 저장하면, Polling 시 사진을 획득해서 Goggles에서 사진 분석을 수행하는 것을 로그로 확인할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    public class CameraActivity extends Activity {
    private final static String DCIM_PATH = Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera";
    // ... 생략 ...
    /**
    * /mnt/sdcard/DCIM 폴더 하위에 Jpeg 파일 저장
    *
    * @param jpegByteArray Jpeg 파일의 ByteArray
    */
    private Uri saveToDCIMFolder(byte[] jpegByteArray) {
    String filename = makeFilename();
    if(FileUtil.isDirectoryExisted(DCIM_PATH) == false) {
    Toast.makeText(this, DCIM_PATH + "가 존재하지 않습니다.", Toast.LENGTH_SHORT).show();
    return null;
    }
    return insertMediaStore(DCIM_PATH, filename, jpegByteArray);
    }
    /**
    * MediaStore에 사진 저장하기
    *
    * @param dirPath 사진을 저장할 Directory 위치
    * @param filename 사진 파일 이름
    * @param jpegByteArray Jpeg 파일의 ByteArray
    * @return MediaStore에 저장된 사진의 URI
    */
    private Uri insertMediaStore(String dirPath, String filename, byte[] jpegByteArray) {
    String filePath = dirPath + "/" + filename;
    try {
    ContentValues values = new ContentValues();
    values.put(Images.Media.DATE_TAKEN, new Date().getTime());
    values.put(Images.Media.ORIENTATION, "0");
    String title = filename.replace(".jpg", "");
    values.put(Images.Media.TITLE, title);
    values.put(Images.Media.DISPLAY_NAME, filename);
    values.put(Images.Media.MIME_TYPE, "image/jpeg");
    values.put(Images.Media.SIZE, jpegByteArray.length);
    values.put("_data", filePath);
    Uri uri = getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
    OutputStream os = getContentResolver().openOutputStream(uri);
    os.write(jpegByteArray);
    os.close();
    Logger.info("MediaStore Inserted URI:" + uri.toString());
    return uri;
    } catch(Exception ex) {
    Logger.error(ex, "Failed to save the Bitmap file. FilePath: %s", filePath);
    Toast.makeText(this, "Bitmap 저장 실패", Toast.LENGTH_SHORT).show();
    }
    return null;
    }
    }

    코드 4 MediaStore를 통해 /mnt/sdcard/DCIM 하위 폴더에 사진을 저장하는 예제 코드

  • 결론

    지금까지 기본 카메라 앱과 Goggles 앱의 연동 방법을 분석해 봤다. 이 연동 방법을 사용해서 기본 카메라 앱에서 촬영한 사진을 활용하는 다양한 앱을 개발할 수 있기를 기대한다.

  • 참고자료

    Picasa Uploader 소스 파일

     

  • 728x90
    반응형
    :
    Posted by mapagilove