임베디드월드

글: 라영호 | ratharn@naver.com / 2013-02-04


[연재 차례]

1. 안드로이드 시스템의 역사 및 동향
2. 안드로이드 시스템과 리눅스
3. 안드로이드 플랫폼의 이해
4. 안드로이드 바인더(Binder)의 이해
5. 안드로이드 서비스
6. 안드로이드 SurfaceFlinger와 프레임버퍼 드라이버
7. 안드로이드 User Interface와 ADK2012
8. Linux Sound Device와 안드로이드 사운드 시스템
9. 안드로이드 카메라 시스템
10. 안드로이드 카메라와 멀티미디어 프레임워크
11. 안드로이드 카메라와 멀티미디어 프레임워크 ②
12. 안드로이드 시스템 디버깅 및 기타


안드로이드 카메라와 멀티미디어 프레임워크, Android Camera and Multimedia Framework

안드로이드 시스템 개발자를 위한 안드로이드 시스템의 분석 및 이해 ⑪
안드로이드 카메라와 멀티미디어 프레임워크 ②

2013년 1월호에서 다루었던 멀티미디어 프레임워크에 핵심적인 부분인 코덱 및 멀티미디어 플레이어의 구조에 대해 살펴보도록 하겠다. 이제 3월호에서 디버깅 및 관련 사항에 대해 살펴보면 본 13회에 걸쳐 다루었던 연재에 대해 마무리가 되겠다. 연재 기간 중 안드로이드 및 리눅스 발전이 급속하게 이루어졌다. 멀티코어가 기본이 되고, 4.1버전의 젤리빈이 안드로이드 운영체제를 탑재하는 메인 운영체제로 자리잡았다.앱 개발 환경 및 시장 상황도 연재를 처음 시작할 때와 많이 달라졌다.이러한 기술적인 트랜드를 연재 과정 중 다루지 못한 것에 대한 아쉬움이 남는다. 이번호에서는 이전호에서 다루지 못한 멀티미디어 코덱 및 제반 기술에 대해 살펴보도록 하겠다. 

코덱(Codec)
멀티미디어는 멀티미디어 파일에 대한 파일 포멧 처리와 코덱 두가지에 그 기본을 두고 있다. 즉, 멀티미디어 파일을 읽어 들이고, 파일을 분석하여 멀티 미디어 정보를 추출하고, 코덱을 이용하여 디코딩하는 일련의 과정이 대부분이다. 멀티미디어 응용 프로그램은 멀티미디어 파일에 대한 재생과 멀티미디어 파일 형식으로 녹화하는 기능을 작성하는 것이 대부분이다.
미디어 플레이어 프로세스에서 일반적으로 플레이어는 미디어 파일을 처리함에 있어 두 개의 과정으로 처리한다. 하나는 파일에 대한 파싱이고, 나머지는 미디어 스트림에 대한 디코딩이다.

예를 들어, mp4 파일을 처리함에 있어, 여기서 이 mp4 파일은 AMR (Adaptive Multi-Rate audio) 혹은 AAC 오디오 스트림 갖고 있고, 비디오 스트림으로는 H263, MPEG4, AVC(H264)를 갖을 수 있다. 이 스트림들은 3GP 패키지의 형태로 패키징되어 있게 된다. 미디어 플레이어는 패키징된 형태로부터 각 스트림을 분리해낸 후, 미디어 스트림들을 디코딩한다. 미디어 처리에서 레코딩은 비디오, 오디오, 이미지 캡쳐 기능과 직접 연관되어 있다. 비디오와 오디오를 하나의 파일로 저장하는 기능은 정확히 플레이어의 반대의 기능이고, 하드웨어 기기로부터 비디오와 오디오 스트림을 획득하고, 이것을 코딩한 후 적절한 형태로 저장을 하게 된다.

OpenCORE는 이 두 개의 파트를 fileformats 과 codecs_v2라는 디렉토리로 각각 분리해 놓았다.
이러한 두 개의 부분은 아주 기본적인 기능들이며, 다른 모듈에 의해서 호출된다.
Node의 구성에서와 같은 모듈에서 호출된다. 멀티미디어 플레이와 레코딩 시에는 두 가지 형태의 파일을 다루는 방법이 존재한다. OpenCORE에서 파일 포맷을 다루는 두 개의 타입은 파서와 컴포우저(component device)라고 부른다. codecs_v2 디렉토리에는 각종 코덱에 대한 코드들이 들어 있다.

fileformats 디렉터리

fileformats
|-- avi
|   `-- parser
|-- common
|   `-- parser
|-- id3parcom
|   |-- Android.mk
|   |-- build
|   |-- include
|  `-- src
|-- mp3
|  `-- parser
|-- mp4
|   |-- composer
|  `-- parser
|-- rawaac
|  `-- parser
|-- rawgsmamr
|  `-- parser
`-- wav
   `-- parser


codecs_v2 디렉토리의 구조

codecs_v2
|-- audio
|  |-- aac
|   |-- gsm_amr
|   |-- mp3
|   `-- sbc
|-- omx
|   |-- factories
|   |-- omx_aac
|   |-- omx_amr
|   |-- omx_common
|   |-- omx_h264
|   |-- omx_m4v
|   |-- omx_mp3
|   |-- omx_proxy
|   `-- omx_queue
|-- standalone_headerfiles
|-- utilities
|   |-- colorconvert
|   |-- m4v_config_parser
|   `-- pv_video_config_parser
`-- video
   |-- avc_h264
      `-- m4v_h263


audio, video 디렉토리에는 각 코덱에 대한 디렉토리가 존재한다. 그 하위 디렉토리에는 dec, enc 와 같은 디코딩, 인코딩에 관련된 코드가 들어있다.

dec, enc 디코딩, 인코딩 관련 코드

`-- video
  |-- avc_h264
  | |-- common
  | |-- dec
  | |-- enc
  | `-- patent_disclaimer.txt
  `-- m4v_h263
    |-- dec
    |-- enc
    `-- patent_disclaimer.txt

codecs_v2 디렉토리는 omx 라는 디렉토리를 가지고 있다. 이 디렉토리에는 khronos 그룹의 OpenMAX 함수들이 들어 있다. OpenMAX는 2006년도에 시작된 NVIDIA에 의한 khronos그룹의 멀티미디어 어플리케이션 스탠다드에 대한 프레임워크다.

OpenMax
OpenMax는 다음과 같이 3개의 Layer로 나뉘어 진다.

● OpenMax AL
● OpenMax IL
● OpenMax DL

OpenMax AL
최상위 레벨의 응용프로그램 레이어로, 플랫폼과 무관하게 미디어 인터페이스를 제공하고 사용할 수 있다. 다만 문제는 고 수준 미디어 제어 프로그램만을 작성할 수 있다. 응용프로그램의 경우 때로는 이 레이어의 기능을 이용하지 않고, 플랫폼에서 응용 프로그램을 만들기도 한다. 저 수준에 해당되는 OpenMax IL을 사용해서도 미디어 플래이어를 만들 수 있기 때문이다.

OpenMax IL
인티그레이션 레이어(Integration Layer)로써, 멀티미디어 코덱들과 사용자들간의 인터페이스를 제공한다. 컴포넌트 기반으로 부품을 설계하듯이 미디어 플레이어를 설계할 수 있다.

OpenMax DL
개발 레이어로써, 오디오 코덱, 비디오 코덱들을 어떻게 만드는지 설명하고 있다.

OpenCORE 플레이어
OpenCORE의 플레이어 Makefile은 build_config/opencore_dynamic/ Android_opencore_player.mk 이다. 컴파일이 끝나게 되면 libopencore_player.so 라이브러리 파일이 생성된다. 이 라이브러리는 두 개의 구성요소로 나눌 수가 있다. 하나는 플레이어의 엔진(PacketVideo의 player engine)
다른하나는 Android component player에대한것이다. 안드로이드에서 packet video의 player engine을 사용할 수 있도록 등록하는 부분

● player engine에 대한 경로는 external/opencore/engine/player 이다
● adapter에 대한 소스 경로는 external/opencore/android이다.

플레이어 엔진부분
플레이어 엔진 부분은 [그림1]에서 보듯이 다른 플레이어에 따른 다른 상황에 대처할 수 있도록 구성되어 있다. 즉, 다른 클래스 혹은 다른 플레이어 구조에서 플레이어 엔진을 사용해도 큰 상관없는 구조로 되어 있다.

플레이어 엔진의 디렉토리 구성

engines/player/
|-- Android.mk
|-- build
| |-- linux_nj
| |-- make
| `-- makefile.conf
|-- config
| `-- linux_nj
|-- include
| |-- pv_player_datasink.h
| |-- pv_player_datasinkfilename.h
| |-- pv_player_datasinkpvmfnode.h
| |-- pv_player_datasource.h
| |-- pv_player_datasourcepvmfnode.h
| |-- pv_player_datasourceurl.h
| |-- pv_player_events.h
| |-- pv_player_factory.h
| |-- pv_player_interface.h
| |-- pv_player_license_acquisition_interface.h
| |-- pv_player_registry_interface.h
| |-- pv_player_track_selection_interface.h
| `-- pv_player_types.h
|-- src
| |-- pv_player_datapath.cpp
| |-- pv_player_datapath.h
| |-- pv_player_engine.cpp
| |-- pv_player_engine.h
| |-- pv_player_factory.cpp
| |-- pv_player_node_registry.h
| `-- pv_player_sdkinfo.h
`-- test
  |-- Android.mk
  |-- build
  |-- config
  `-- src

플레이어 엔진의 구조
[그림 1] 플레이어 엔진의 구조

이 중 engines/player/include 디렉토리에는 인터페이스 관련 header 파일들이 들어 있다. engines/player/src 디렉토리에는 소스들과 private header 파일들이 있다.
여기서 중요한 헤더파일들은 다음과 같다.

pv_player_types.h - data structure들과 enumeration value 들에 대한 정의
pv_player_events.h - UUID 값과 각 에러값에 대한 정의
pv_player_datasink.h - datasink는 media data에 대한 출력을 정의한다. PVPlayerDataSink에 대한 정의가 있고, 이것은 media data output 인터페이스에 대한 기본 클래스다.
pv_player_datasinkfilename.h - PVPlayerDataSinkFilename 카테고리 정의.
pv_player_datasinkpvmfnode.h - PVPlayerDataSinkPVMFNode카테고리 정의
pv_player_datasource.h - PVPlayerDataSource, 미디어 데이터 입력 부분 정의
pv_player_datasourcepvmfnode.h - PVPlayerDataSourcePVMFNode카테고리 정의
pv_player_datasourceurl.h - PVPlayerDataSourceURL카테고리 정의
pv_player_interface.h - PVPlayerInterface정의
pv_player_factory.h - PVPlayerFactory정의

engines/player/src 디렉토리의 메인은 PVPlayerEngine 클래스가 정의되어 있는 pv_player_engine.cpp 이다. PVPlayerEngine은 PVPlayerInterface로부터 상속된 클래스이다.
이것은 PVPlayerFactory가 생성이 될 때 PVPlayerInterface로 생성이 되지만, 실제 생성은 PVPlayerEngine의 형태로 생성이 된다.
PVPlayerInterface 인터페이스는 이러한 것들을 위한 기본 동작에 대한 정의가 되어있다.


메인 인터페이스
engines/player/src/pv_player_engine.h

PVCommandId AddDataSource(PVPlayerDataSource& aDataSource, const OsclAny* aContextData = NULL);
PVCommandId Init(const OsclAny* aContextData = NULL);
PVCommandId AddDataSink(PVPlayerDataSink& aDataSink, const OsclAny* aContextData = NULL);
PVCommandId Prepare(const OsclAny* aContextData = NULL);
PVCommandId Start(const OsclAny* aContextData = NULL);
PVCommandId Pause(const OsclAny* aContextData = NULL);
PVCommandId Resume(const OsclAny* aContextData = NULL);
PVCommandId Stop(const OsclAny* aContextData = NULL);
PVCommandId RemoveDataSink(PVPlayerDataSink& aDataSink, const OsclAny* aContextData = NULL);
PVCommandId Reset(const OsclAny* aContextData = NULL);
PVCommandId RemoveDataSource(PVPlayerDataSource& aDataSource, const OsclAny* aContextData = NULL);

위에는 비디오와 오디오 출력을 위하여 DataSink 관련 함수들을 포함하고 있다. pv_player_types.h 에는 PVP_STATE_ 로 시작되는 플레이어의 스테이트 머신의 상태를 나타내는 상태들이 선언되어 있다.

스테이트 머신의 상태 선언

typedef enum
{
  PVP_STATE_IDLE = 1,
  PVP_STATE_INITIALIZED = 2,
  PVP_STATE_PREPARED = 3,
  PVP_STATE_STARTED = 4,
  PVP_STATE_PAUSED = 5,
  PVP_STATE_ERROR = 6
} PVPlayerState;

PVPlayerInterface는 각 동작이 성공하였을 경우 플레이어의 스테이트 머신을 변화시킬 수 있다.
player가 초기상태에 있을 때 스테이트는 PVP_STATE_IDLE이 되고, Init 호출이 끝난 후에는 PVP_STATE_INITIALIZED 상태로 들어간다. AddDataSink를 호출하는 것은 PVP_STATE_PREPARED로 접근하는 것이고, Prepare를 호출한 후에는 PVP_STATE_PREPARED 상태로 들어간다.

start 함수를 호출해서 PVP_STATE_STARTED로 들어간 후 pause를 호출해서 PVP_STATE_PAUSED 상태로 될 수 있다. PVP_STATE_STARTED, PVP_STATE_PAUSED 이 두 개의 상태는 플레이 상황에서 나오는 상태이며 각각 start와 pause 함수를 호출해서 이 두 개의 상태를 스위치 할 수 있다.

playback 상황에서는 stop을 호출해서 PVP_STATE_INITIALIZED 를 return할 수 있으며, RemoveDataSource 함수를 호출해서 PVP_STATE_IDLE 상태를 return 할 수 있다.

참고로, pv_player_engine.cpp 내부적으로는 PVP_ENGINE_STATE_ 의 prefix의 스테이트를 사용하고, 위의 PVP_STATE_ 의 경우는 PVP_ENGINE_STATE_ 의 상태를 적절히 체킹하여 스테이트를 표시하게 되어 있다.(engine/player/src/pv_player_engine.h 파일 참고)

Android 소스 중 안드로이드용 opencore player의 adapter 는 external/opencore/android 디렉토리에 있고, 그 파일 리스트는 다음과 같다.

안드로이드의 플레이어의 어뎁터는 OpenCORE 플레이어 엔진의 접속 인터페이스를 호출해야한다. 안드로이드 미디어 플레이어 인터페이스에 대한 구현은 PVPlayer의 구현이다.


Adapter 파일리스트

android
|-- Android.mk
|-- android_audio_mio.cpp
|-- android_audio_mio.h
|-- android_audio_output.cpp
|-- android_audio_output.h
|-- android_audio_output_threadsafe_callbacks.cpp
|-- android_audio_output_threadsafe_callbacks.h
|-- android_audio_stream.cpp
|-- android_audio_stream.h
|-- android_log_appender.h
|-- android_surface_output.cpp
|-- android_surface_output.h
|-- mediascanner.cpp
|-- metadatadriver.cpp
|-- metadatadriver.h
|-- playerdriver.cpp
|-- playerdriver.h
`-- thread_init.cpp

PVPlayer(frameworks/base/include/media/PVPlayer.h에 정의)는 MediaPlayerInterface로부터 상속받은 것이다. 플레이어 구현과정의 처음은 PlayerDriver를 얻어내는 것이고, 이 PlayerDriver는 PVPlayer를 사용한다. PVPlayer는 PlayerDriver의 특정함수를 호출함으로써 생성되며, 전체 구현에 대한 구조도는 [그림2]와 같다.

PVPlayer 구조도
[그림 1] PVPlayer 구조도

MediaPlayer에서 플레이어를 제어하기 위하여 PVPlayerDriver에는 다양한 동작에 대한 다양한 명령을 사용한다. 이런 것은 external/opencore/android/playerdriver.h에 정의 되어 있다.


플레이어 제어 명령

class PlayerCommand
{
public:
  enum Code {
  PLAYER_QUIT = 1,
  PLAYER_SETUP = 2,
  PLAYER_SET_DATA_SOURCE = 3,
  PLAYER_SET_VIDEO_SURFACE = 4,
  PLAYER_SET_AUDIO_SINK = 5,
  PLAYER_INIT = 6,
  PLAYER_PREPARE = 7,
  PLAYER_START = 8,
  PLAYER_STOP = 9,
  PLAYER_PAUSE = 10,
  PLAYER_RESET = 11,
  PLAYER_SET_LOOP = 12,
  PLAYER_SEEK = 13,
  PLAYER_GET_POSITION = 14,
  PLAYER_GET_DURATION = 15,
  PLAYER_GET_STATUS = 16,
  PLAYER_REMOVE_DATA_SOURCE = 17,
  PLAYER_CANCEL_ALL_COMMANDS = 18,
};

이런 명령의 일반적인 구현은 PVPlayerInterface 각 부분에 대해서 간단한 인터페이스로 구현된다.
예를 들어 player pause 동작에 대해서 전체 시스템에서의 구현의 흐름은 다음과 같다.


PVPlayer 중의 pause 함수 구현

status_t PVPlayer::pause()
{
  LOGV("pause");
  return mPlayerDriver->enqueueCommand(new PlayerPause(0,0));
}



여기서 mPlayerDriver는 PlayerDriver 형의 변수이고, PlayerPause class를 하나 생성하면서 command를 큐에 집어넣는 역할을 한다. PlayerPause 클래스는 playerdriver.h 파일에 선언되어 있다.

PlayerDriver는 간접적으로 enqueueCommand 함수를 이용해서 메시지 처리에 대한 호출을 하고, PlayPause 명령에 대해서 결과적으로는 handlePause()함수를 호출하게 된다.


handlePause()함수 호출

void PlayerDriver::handlePause(PlayerPause* ec)
{
  LOGV("call pause");
  mPlayer->Pause(0);
  FinishSyncCommand(ec);
}

여기서 mPlayer는 PVPlayerInterface형의 포인터이며, 이 포인터는 OpenCORE의 player engine 타입인 PVPlayerEngine을 호출하기 위해서 사용된다.(PVPlayerEngine은 PVPlayerInterface서부터 상속됨) 플레이어의 어뎁터 구현에 있어 안드로이드 프레임워크에서 해야 할 중요한 일은 미디어 아웃풋을 정의하는 것이다. OpenCORE는 플레이어 엔진의 형태로 이러한 것들을 변환할 필요가 있다. 여기서 두 개의 중요한 타입이 존재하게 되는데,

● Android Surface Output 을 담당하는 android_surface_output.cpp
● Android Audio Output을 담당하는 android_audio_output.cpp

비디오 아웃풋을 설정하기 위해서는 PlayerDriver에 세 개의 멤버가 사용된다.


플레이어 엔진 인터페이스

external/opencore/android/playerdriver.cpp
  PVPlayerDataSink *mVideoSink;
  PVMFNodeInterface *mVideoNode;
  PvmiMIOControl *mVideoOutputMIO;
}

위에서 mVideoSink는 PVPlayerDataSink 타입이고, 이것은 플레이어 엔진 인터페이스이다. mVideoNode는 PVMFNodeInterface 타입이고, 이것은 external/opencore/pvmi/pvmf/include/pvmf_node_interface.h 에 정의되어 있으며, PVMF에 접근하는 모든 NODE에 통일된 인터페이스를 제공하는 클래스이다.

PvmiMIOControl 타입인 mVideoOutputMIO 는 external/opencore/pvmi/pvmf/include//pvmi_mio_control.h 파일에 정의되어 있으며, 이 클래스는 미디어인풋/아웃풋 제어를 위한 추상화된 인터페이스를 제공한다. PVPlayer에 있는 setVideoSurface는 비디오 아웃풋 인터페이스를 설정한다.


setVideoSurface는 비디오 아웃풋 인터페이스 설정

status_t PVPlayer::setVideoSurface(const sp<ISurface>& surface)
{
  LOGV("setVideoSurface(%p)", surface.get());
  mSurface = surface;
  return OK;
}


실제 비디오 아웃풋인터페이스에 대한 세팅은 run_set_video_surface() 함수에서 이루어진다.


run_set_video_surface() 함수

void PVPlayer::run_set_video_surface(status_t s, void *cookie)
{
  LOGV("run_set_video_surface s=%d", s);
  if (s == NO_ERROR) {
    PVPlayer *p = (PVPlayer*)cookie;
    if (p->mSurface == NULL) {
      run_set_audio_output(s, cookie);
    } else {
      p->mPlayerDriver->enqueueCommand(new PlayerSetVideoSurface(p->mSurface, run_set_audio_output, cookie));
    }
  }
}


이 함수는 void PVPlayer::run_init(status_t s, void *cookie, bool cancelled)에서 호출되고 PlayerSetVideoSurface 함수의 호출은 결국은 handleSetVideoSurface() 함수를 호출하게 된다.


OpenCORE의 동작 설명
OpenCore는 각각의 Node를 관리, 제어하는 PVPlayerEngine이 있다. PVPlayerEngine을 구동하기 위한 과정은 PlayerDriver 에서 player thread를 시작, PVPlayerFactory를 이용하여 PVPlayerEngine을 생성한다. PVPlayerEngine이 생성되면 응용프로그램은 PVPlayerEngine에서 제공하는 Command API (Command Queue로 명령을 전달하는 API)로 Source, Sink, Decode Node를 만들고(준비하고) Play, Stop, Pause등의 컨트롤을 하게된다. 엔진에서 파일 Play를 위한 기본 과정을 순서대로 정리하면 다음과 같다.

1. AddDataSource()
2. Init()
3. AddDataSink() : 비디오 출력용
4. AddDataSink() : 오디오 출력용
5. Prepare()
6. Start()
7. Stop()


OpenCORE Node 등록
OpenCore의 Node는 데이터 처리를 위한 기본 단위이며 OpenMax IL의 컴포넌트에 해당한다.
OpenCore는 리눅스에서 사용하는 OpenMAX의 코덱 컴포넌트를 이용하기 위하여 코덱 노드에 OMX Component 개념을 결합시킨 것이다. 각각의 Node는 BaseDec Node(pvomxbasedecnode)로 부터 상속되었다.

external/opencore/nodes/pvomxbasedecnode/src/pvmf_omx_basedec_node.cpp
OpenMAX IL 컴포넌트와 연결되는 부분이 BaseDec Node 부분이다. 이러한 Node들을 사용하기 위해서는 등록 또는 해제 과정이 먼저 필요하고, 이를 위한 함수가 RegisterAllNodes()와 UnregisterAllNodes()이다. external/opencore/engines/player/config/core/Pv_player_node_registry_populator.cpp
Node를 등록하는 과정은 InputType과 OutputType을 설정하고 고유 ID (UUID)를 정의하고 Create와 Delete를 위한 Callback 함수를 정의한 후, aRegistry->RegisterNode(nodeinfo)를 호출하여 등록하면 된다. 등록된 Node를 생성하는 방법은 PVPlayerEngine의 DoPrepare 과정을 보면 알 수 있다. DoPrepare에서 DoDecNodeQueryCapConfigIF()를 호출하게 되는데 이를 보면 InputType과 OutputType에 맞는 UUID를 검색해 내서 필요한 Node를 생성하는 것을 볼 수 있다.

(external/opencore/engines/player/src/Pv_player_engine.cpp).
QueryRegistry()는 InputType과 OutputType 정보를 이용하여 등록된 Node를 검색하여 UUID를 반환한다.(external/opencore/engines/player/src/Pv_player_node_registry.cpp)
CreateNode()는 UUID를 이용하여 등록된 Node의 Create Callback 함수를 호출한다.
OpenCore의 Recognizer는 입력 소스로부터 필요한 Parser Node와 Decode Node 등을 선택할 수 있도록 해준다. Node를 등록한 것처럼 RegisterAllRecognizers()와 UnregisterAllRecognizers()로 Recognizer를 등록/해제할 수 있다.
external/opencore/engines/player/config/core/Pv_player_node_registry_populator.cpp
Recognizer들도 Node와 마찬가지로 RecognizerPlugIn과 해당 Factory로 이루어져있다.
MKV는 external/opencore/pvmi/recognizer/plugins/pvmkvffrecognizer/src/pvmkvffrec_factory.cpp
에서 팩토리를 이용해 RecognizerPlugIn 을 생성한다.


external/opencore/pvmi/recognizer/plugins/pvmkvffrecognizer/src/pvmkvffrec_factory.cpp

OSCL_EXPORT_REF PVMFRecognizerPluginInterface* PVMKVFFRecognizerFactory::CreateRecognizerPlugin()
{
  PVMFRecognizerPluginInterface* plugin = NULL;
  plugin = OSCL_STATIC_CAST(PVMFRecognizerPluginInterface*, OSCL_NEW(PVMKVFFRecognizerPlugin, ()));
  if (plugin == NULL)
  {
    OSCL_LEAVE(OsclErrNoMemory);
  }
  return plugin;
}


external/opencore/pvmi/recognizer/plugins/pvmkvffrecognizer/src/pvmkvffrec_plugin.cpp

PVMFStatus PVMKVFFRecognizerPlugin::Recognize(PVMFDataStreamFactory& aSourceDataStreamFactory, PVMFRecognizerMIMEStringList* aFormatHint,
Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult)
{
  OSCL_UNUSED_ARG(aFormatHint);

  gprintf(" ##Ho PVMKVFFRecognizerPlugin::Recognize");
  LOGV("PVMKVFFRecognizerPlugin::Recognize");
  OSCL_wStackString<1> tmpfilename;
  Oscl_FileServeriFileServer;
  int32 leavecode = 0;
  iFileServer.Connect();
  LOGV("PVMKVFFRecognizerPlugin::Recognize File %s", tmpfilename.get_cstr());
  //Verify whether file is valid MKV or not
PVMkvFile::IsMkvFile(tmpfilename, leavecode, &iFileServer,&aSourceDataStreamFactory);


PVMFRecognizerResult result;
if (PV_MKV_FILE_PARSER_SUCCESS == leavecode)
  {
    gprintf("PVMKVFFRecognizerPlugin::Recognize MKV Successfully");
    LOGV("PVMKVFFRecognizerPlugin::Recognize MKV Successfully");
    // It is an MKV file so add positive result
    result.iRecognizedFormat = PVMF_MIME_MKVFF;
    result.iRecognitionConfidence = PVMFRecognizerConfidenceCertain;
    aRecognizerResult.push_back(result);
  }

  return PVMFSuccess;
}

Recognizer Factory를 등록한 후 나중에 필요한 경우에 PVMKVFFRecognizerPlugin::Recognize()를 호출하여 MKV 파일인지 아닌지를 확인하도록 한다. Recognizer()가 실제 호출되는 시점은 AddDataSource() 를 호출하는 과정에서 Recognizer()가 실행 될 때이다.

AddDataSource는 파일의 포맷을 알아내고 AddDataSink는 Video 또는 Audio 출력 포트에 대한 정보를 저장해 두는 역할을 한다. 각각의 Sink Node, Decode Node, Parser(Source) Node를 생성하고 연결하는 모든 작업은 Prepare()에서 이루어진다.

Prepare()를 호출하면 결국 PVPlayerEngine에서는 DoPrepare()가 호출되는데 DoPrepare()를 보면 내부 스테이트를 관리하여 4가지 단계로 동작한다.

1. PVP_ENGINE_STATE_INITIALIZED
2. PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE
3. PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE
4. PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE
이 전체 과정은 출력단으로부터 입력단까지 순차적으로 Node를 생성하고 연결한다. PVP_ENGINE_STATE_ INITIALIZED 상태에서는 Sink Node를 설정하면서 Track Selection을 시작 하게 된다.


PVP_ENGINE_STATE_TRACK_
SELECTION_1_DONE

Sink nodes의 초기화가 끝난 후에 Engine은 PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE 상태가 되고 Dec node를 생성하고 필요할 경우 Dec node의 초기화를 실행.
DoSinkNodeTrackSelection(), DoDecNodeQueryCapConfigIF(), DoDecNodeInit() 진행한다.


PVP_ENGINE_STATE_TRACK_
SELECTION_2_DONE

Dec node의 초기화가 완료되면 PVP_ENGINE_STATE_ TRACK_SELECTION_2_DONE 상태로 전이된다.
각각의 트랙들에 대한 파라메터들을 확인한 후 Playble List를 확보한다.
Engine은 트랙을 선택한 후에 Sink와 Dec 노드들에 대해 Reset을 호출하게 된다. DoSourceNodeTrackSelection(), DoTrackSelection(), DoSinkNodeDecNodeReset() 호출하게 된다.


Track 관련 설정
PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE
Sink, Dec 노드들에 대해 Reset을 완료한 후엔진은 PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE 상태로 전이되고 Engine은 사용하지 않는 모든 Dec Node 들을 제거 하게 된다. DoSinkDecCleanupSourcePrepare(), DoSourceNodePrepare(), DoSinkNodeQueryInterfaceOptional(), DoDatapathPrepare() 순서로 호출한다. DoSourceNodePrepare()를 호출하여 Parser Node를 생성하게 한다.
지금까지 안드로이드 시스템에서 사용하는 카메라 시스템과 멀티미디어 프레임워크에서 살펴봤다. 다음호에서는 전원관리 부분과 디버깅 및 기타 이슈에 대해 살펴 보도록 하겠다.



/필/자/소/개/

필자

라영호

국내 스마트폰의 초창기인 Cellvic에서부터 스마트폰을 개발하였고 윈도 모바일 및 다양한 임베디드 시스템을 개발하고 있다. 현재는 안드로이드 시스템 포팅 및 임베디드 시스템 개발, 컨설팅, 교육 등을 진행하는 회사를 운영하고 있다. 개인 블로그(www.embeddedce.com)를 통해 임베디드 시스템개발에 대한 다양한 생각과 방법론을 함께 생각해 보고자 노력 중이다.

※ 본 내용은 (주)테크월드(http://www.embeddedworld.co.kr)의 저작권 동의에 의해 공유되고 있습니다.
    Copyright ⓒ Techworld, Inc. 무단전재 및 재배포 금지

맨 위로
맨 위로