목적
1. gvideo 에서 MediaPlayer 와 MediaPlayerService 간의 binder통신 부분을 이해한다. (이건 나중에)
2. MediaPlayer 에서 OpenCore로 연결되는 부분을 찾아낸다. (이것도 나중에)
3. OpenCore의 동작 과정을 알고 모듈별로 진행되는 샘플을 만들어보자

frameworks/base/include/media/Mediaplayer.h 에 보면 아래와 같은 함수들이 있고 



class MediaPlayer : public BnMediaPla


yerClient
{
public:
    MediaPlayer();
    ~MediaPlayer();
            void            onFirstRef();
            void            disconnect();
            status_t        setDataSource(const char *url);
            status_t        setDataSource(int fd, int64_t offset, int64_t length);
            status_t        setVideoSurface(const sp<Surface>& surface);
            status_t        setListener(const sp<MediaPlayerListener>& listener);
            status_t        prepare();
            status_t        prepareAsync();
            status_t        start();
            status_t        stop();
            status_t        pause();
            bool            isPlaying();

            status_t        getVideoWidth(int *w);
            status_t        getVideoHeight(int *h);
            status_t        seekTo(int msec);
            status_t        getCurrentPosition(int *msec);
            status_t        getDuration(int *msec);
            status_t        reset();

            status_t        setAudioStreamType(int type);
            status_t        setLooping(int loop);
            bool            isLooping();
            status_t        setVolume(float leftVolume, float rightVolume);
            void            notify(int msg, int ext1, int ext2);
    static  sp<IMemory>     decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
    static  sp<IMemory>     decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);

frameworks/base/include/media/PVPlayer.h에는 아래와 같은 함수들이 있다.

 class PVPlayer : public MediaPlayerInterface
{
public:
                        PVPlayer();
    virtual             ~PVPlayer();

    virtual status_t    initCheck();
    virtual status_t    setDataSource(const char *url);
    virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
    virtual status_t    setVideoSurface(const sp<ISurface>& surface);
    virtual status_t    prepare();
    virtual status_t    prepareAsync();
    virtual status_t    start();
    virtual status_t    stop();
    virtual status_t    pause();
    virtual bool        isPlaying();
    virtual status_t    seekTo(int msec);
    virtual status_t    getCurrentPosition(int *msec);
    virtual status_t    getDuration(int *msec);
    virtual status_t    reset();

    virtual status_t    setLooping(int loop);
    virtual player_type playerType() { return PV_PLAYER; }

    // make available to PlayerDriver
    void        sendEvent(int msg, int ext1=0, int ext2=0) { MediaPlayerBase::sendEvent(msg, ext1, ext2); }

private:
    static void         do_nothing(status_t s, void *cookie, bool cancelled) { }
    static void         run_init(status_t s, void *cookie, bool cancelled);
    static void         run_set_video_surface(status_t s, void *cookie, bool cancelled);
    static void         run_set_audio_output(status_t s, void *cookie, bool cancelled);
    static void         run_prepare(status_t s, void *cookie, bool cancelled);

    PlayerDriver*

이렇게 매칭 되는 부분이 binder로  연결되는 것 같다. 이부분을 빨리 찾자


 
int main(int argc, char** argv) 

   gprintf("entering main..."); 
   sp<ProcessState> proc = ProcessState::self(); 
   proc->startThreadPool(); 
   MediaPlayer mediaplayer; 
   if(argc > 1) 
   { 
       gprintf("set datasource: %s", argv[1]); 
       mediaplayer.setDataSource(argv[1]); 
   } 
   else 
   { 
       gprintf("set datasource: /aa/test.mp4"); 
       mediaplayer.setDataSource("/aa/test.mp4"); 
   } 
 
   gprintf("create SurfaceComposerClient"); 
   int pid = getpid(); 
   int nState = 0; 
   sp<SurfaceComposerClient> videoClient = new SurfaceComposerClient; 
 
   gprintf("create video surface"); 
   //sp<surface> videoSurface(videoClient->createSurface(pid, 0, 176, 144, PIXEL_FORMAT_OPAQUE, ISurfaceComposer::eFXSurfaceNormal|ISurfaceComposer::ePushBuffers)); 
   sp<Surface> videoSurface(videoClient->createSurface(pid, 0, 320, 240, PIXEL_FORMAT_OPAQUE, ISurfaceComposer::eFXSurfaceNormal|ISurfaceComposer::ePushBuffers)); 
   videoClient->openTransaction();
   //nState = videoSurface->setSize(176, 144); 
   //gprintf("videosurface->setSize, %d", nState); 
   //nState = videoSurface->setPosition(0, 0); 
   //gprintf("videosurface->setPosition, %d", nState); 
 
   //set toppest z-order 
   nState = videoSurface->setLayer(INT_MAX); 
   gprintf("videosurface->setLayer, %d", nState); 
   nState = videoSurface->show(); 
   gprintf("videosurface->show, %d", nState); 
   videoClient->closeTransaction(); 
 
   gprintf("set video surface to player"); 
   mediaplayer.setVideoSurface(videoSurface); 
 
   status_t retCode = mediaplayer.prepare(); 
   if(retCode < 0) 
   { 
       gprintf("prepare failed: %d\n", retCode); 
       IPCThreadState::self()->stopProcess(); 
       return -1; 
   }; 
 
   mediaplayer.start(); 
   for(int i=0; i < 10; i++) 
   { 
       sleep(1); 
       gprintf("playing, i=%d\n", i); 
   } 
   mediaplayer.reset(); 
   gprintf("quiting..."); 
 
   //close binder fd, still need waiting for all binder threads exit? 
   IPCThreadState::self()->stopProcess(); 
   return 0; 



sp<ProcessState> proc = ProcessState::self(); 

\frameworks\base\include\utils\ProcessState.h 에
 
namespace android {
// Global variables
extern int                 mArgC;
extern const char* const*  mArgV;
extern int                 mArgLen;
class IPCThreadState;
class ProcessState : public virtual RefBase
{
public:
    static  sp<ProcessState>    self();

\frameworks\base\libs\utils\ProcessState.cpp 에
 
sp<ProcessState> ProcessState::self()
{
    if (gProcess != NULL) return gProcess;
   
    AutoMutex _l(gProcessMutex);
    if (gProcess == NULL) gProcess = new ProcessState;
    return gProcess;
}




Application에서 파일 Play를 위한 기본 과정을 순서대로 정리하면 다음과 같다고 바람진님 블로그에 써있었다.

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

mediaplayer.setDataSource(argv[1]); 

setDataSource 는 재생할 영상을 설정 하는 것 같다.
이것은 frameworks/base/media/libmedia/Mediaplayer.cpp 에 나와있다.

status_t MediaPlayer::setDataSource(const sp<IMediaPlayer>& player)
status_t MediaPlayer::setDataSource(const char *url)
status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)

이렇게 세가지로 오버로딩 되어 있다.
여기서 사용되는 것은 두번째 인것 같다.

status_t MediaPlayer::setDataSource(const char *url)
{
    LOGV("setDataSource(%s)", url);
    status_t err = BAD_VALUE;
    if (url != NULL) {
        const sp<IMediaPlayerService>& service(getMediaPlayerService());
        if (service != 0) {
            sp<IMediaPlayer> player(service->create(getpid(), this, url));
            err = setDataSource(player);
        }
    }
    return err;
}


과정을 보면
입력 소스 주소가 들어오면 MediaPlayerService를 생성한다.
그리고 서비스가 잘 생성되었으면
Mediaplayer를 MediaPlayerService에 요청하는 방법으로 하나만들어서 (프로세스처럼 fork되는것 같다.) 그 생성된 플레이어에 pid를 부여하고 재생할 소스를 던져주는것 같다.

여기에서
1. 서비스 생성하는 부분
2. 플레이어 생성하는 부분

을 보면 될것 같다.

우선 서비스 생성 부분을 보자.

const sp<IMediaPlayerService>& service(getMediaPlayerService());

이것은 sp<IMediaPlayerService>& 형의 MediaPlayerService인터페이스 주소를 리턴한다.
인자로 넘어가는 getMediaPlayerService()따라가보자

getMediaPlayerService()도 frameworks/base/media/libmedia/Mediaplayer.cpp 에 있다.

 
SortedVector< wp<MediaPlayer> > MediaPlayer::sObitRecipients;
// establish binder interface to service
const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService()
{
    Mutex::Autolock _l(sServiceLock);
    if (sMediaPlayerService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16("media.player"));
            if (binder != 0)
                break;
            LOGW("MediaPlayerService not published, waiting...");
            usleep(500000); // 0.5 s
        } while(true);
        if (sDeathNotifier == NULL) {
            sDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(sDeathNotifier);
        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    LOGE_IF(sMediaPlayerService==0, "no MediaPlayerService!?");
    return sMediaPlayerService;
}

마지막에 리턴값이 sMediaPlayerService 인데, 이것은 위에 보면
sMediaPlayerService = interface_cast<IMediaPlayerService>(binder); 이다.

더보기

 
그리고 이 binder
sp<IBinder> binder;
binder = sm->getService(String16("media.player"));
이렇게 생성된다.

sp<IServiceManager> sm = defaultServiceManager(); 니까 sm은 Interface 형식의 ServiceManager 이다.
ServiceManager 의 getService 함수에 media.player 라는 이름을 넘겨서 그것을 동작하게 하는것 같다.

그리고 동작시킨 것을 binder에 넣고, 또 이것을 인터페이스 변환해서, MediaPlayerService 에 넣고 서비스를 띄우게 되는것 같다.
 
플레이어를 생성해서 서비스에 넣는 부분을 보자.
sp<IMediaPlayer> player(service->create(getpid(), this, url));
아 이게 도대체 뭐지? player 라는 함수가 어디있는것인지 모르겠다.

frameworks/base/include/media/IMediaPlayer.h 에 보면 이런게 있는데....
class IMediaPlayer: public IInterface
{
public:
    DECLARE_META_INTERFACE(MediaPlayer);
DECLARE_META_INTERFACE(MediaPlayer); 이것이 아래와 같이 되고
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const String16 descriptor;                                   \
    static sp<I##INTERFACE> asInterface(const sp<IBinder>& obj);        \
    virtual String16 getInterfaceDescriptor() const;                    \
이렇게 해서 나온 Interface 객체가 player로 들어가게 되는것인가? -_-;; 아우~ 모르겠다.
아무튼간에 이런식으로
gvideo 에서 mediaplayer.setDataSource(argv[1]);   부분이 작동하게 되면, MediaPlayerService 와 MediaPlayer가 생성되는것 같다.

다음으로는
sp<SurfaceComposerClient> videoClient = new SurfaceComposerClient; 
이 놈은. 비디오를 뿌려주기 휘새 SurfaceFlinger 쪽을 생성하는놈으로 추측된다.

SurfaceComposerClient 라는 객체를 하나 생성하는데 다음처럼 초기화 된다.
   sp<Surface> videoSurface(videoClient->createSurface(pid, 0, 320, 240, PIXEL_FORMAT_OPAQUE, ISurfaceComposer::eFXSurfaceNormal|ISurfaceComposer::ePushBuffers)); 
여기에서 pid 는 현재 프로세스의 pid가 넘어가니까 새로 생성되는 SurfaceComposerClient 객체는 부모의 pid를 간직하고 있게 되는것 같다.
그리고 출력 해상도랑 ISurfaceComposer등등의 자료가 넘어간다.

이렇게 초기화된 SurfaceComposerClient 객체는 이제
   videoClient->openTransaction();
openTransaction() 이라는 놈을 호출한다.
이놈은 frameworks/base/libs/ui/SurfaceComposerclient.cpp 에 다음처럼 선언되어 있다.
status_t SurfaceComposerClient::openTransaction()
{
    if (mStatus != NO_ERROR)
        return mStatus;
    Mutex::Autolock _l(mLock);
    VERBOSE(   "openTransaction (client %p, mTransactionOpen=%d)",
            this, mTransactionOpen);
    mTransactionOpen++;
    if (mPrebuiltLayerState == 0) {
        mPrebuiltLayerState = new layer_state_t;
    }
    return NO_ERROR;

 
헤더를 보면 framworks/base/include/ui/SurfaceComposerClient.h
class SurfaceComposerClient : virtual public RefBase
{
(중략 ....)
  //! Create a surface
    sp<Surface>   createSurface(
            int pid,            //!< pid of the process the surfacec is for
            DisplayID display,  //!< Display to create this surface on
            uint32_t w,         //!< width in pixel
            uint32_t h,         //!< height in pixel
            PixelFormat format, //!< pixel-format desired
            uint32_t flags = 0  //!< usage flags
 
위에서 생성했던것 이 어디 들어가는지 나오는데. 난 여기에서 mStatus 가 바뀌는줄 알고 찾았더니 없네. -_-;;
일단 Mutex Lock을 거는데. 이건 화면버퍼에 기록할 동안 락 걸려고 이러는건가...?
그리고 mTransactionOpen++ 를 해주는데 참조카운트를 할려고 하는거겠지?
그리고 mPrebuiltLayerState 미리 빌드된 레이어가 있는지 확인하고 없으면 새로 만드는 과정이 있다.
Transaction이라는 말이 들어가서 바인터쪽으로 또 파고 들어가는줄 알았는데 여긴 바인더관련 부분이 없는것 같네.... -_-;;


여기서 잠시 쉬고 바인더쪽 공부하고 와서 다시 봐야겠다. @_@;;; 아 졸려. 

아~ 삽질했다.
다시 시작하자.

이 윗부분은 그냥 삽질 한거다. 나중에 시간날때나 봐야지....
이제부터는 OpenCore의 동작 과정을 살펴보겠다.

일단
external/opencore/android/ 디렉토리로 가보자.
여러개의 파일이 있는데 어디서부터 살펴볼까?
파일이름을 가만히 보니.

autodetect.cpp 이놈은 코덱 찾아주는것 같고.
mediascanner.cpp 이놈도 뭔가 스캔하는것 같은데 코덱이나 파일포맷을 스캔하는걸까?
playerdriver.cpp 이놈은 뭔가 하드웨어 처리를 하는것 같기도 하고....

일단 잘 모르니까 아무거나 찍자.
autodetect.cpp 부터 보자.
보기전에 autodetect.h를 봤는데 가능한 Encoding 을 찾는 함수가 있다. 이놈은 내가 찾는 놈이 아닌듯 싶다. 제끼자.

다음은 external/opencore/android/mediascanner.cpp 이놈을 봐보도록 하자.
아 딱 열어 보니 이놈부터 보는게 맞는것 같다는 생각이 든다.
맨 윗줄에 있는
#include <media/mediascanner.h>
를 보니 헤더부터 보고싶어졌다.

frameworks/base/include/media/Mediascanner.h 를 보니까.
class MediaScanner , class MediaScannerClient 가 떡하니 있다.
이것 마져도 서버 클라이언트 개념인가?
서버쪽에는
파일이랑 디렉토리관련된것도 보이고, 클라이언트 객체주소를 받아서 쓰는 함수도 있고, 앨범아트 추출함수도 보이고, doProcessDirectory라는 do 가 붙은 함수도 보이고, initializeForThread라는 함수도 보인다. 왠지 do어쩌구랑 쓰레드가 중요할 것 같은 느낌이 든다.

클라이언트 클래스를 보니 beginFile과 endFile이라는 것이 눈에 들어오고 scanFile, setMimeType 이 왠지 의심스럽다.

잘 모르는데 코드 따라다니기도 그렇고 하니 의심스러운 함수에다가 일단 로그 다 찍고 돌려보도록 하자.

로그를 찍을려고 external/opencore/android/mediascanner.cpp 를 보니까 신기한 함수들이 많다. parser 관련도 많고....
로그를 찍다보니 함수마다 InitializeForThread(); 로 시작을 하는 것을 발견!
일단 다 찍고 다시 봐야지.

로그 찍어본 결과
OpenCore의 external/opencore/android/mediascanner.cpp 에서
processDirectory() 함수와 doProcessDirectory()함수가 안드로이드 부팅되면서 순서대로 자동 실행됨을 알 수 있고
doProcessDirectory() 함수 중간에 /system/media라는 있지도 않은 디렉토리를 한번 열어보는 과정이 있는데 왜있는지 모르겠다.

아! 지금 함수 목록을 다시 보니
parseMidi, parseMP3, parseMP4, parseOgg, parseWMA 밖에 없네!?
C100 Android rootfs에서 AVI부분을 뜯어다가 붙여서 지금 AVI도 되는 상태인데..??
이쪽은 오디오만 하는건가? 아니면 이쪽이 아닌건가?

비디오를 실행하면
PlayerDriver
PVMediaOutputNodePort
일단 이렇게 두개가 찍힌다.

아웅.... 똥마려 내일 다시 봐야지 -_-;; 


주말동안 쉬었더니 감을 잃었다. 별로 해논것도 없었지만. -_-;; 오늘은 바람진님의 블로그에 나와있는 함수들에 전부 로그를 찍어보면서 시작해보자.

#undef LOG_TAG
#define LOG_TAG "pv_player_factory"
#include <utils/Log.h>

OpenCore는 각각의 Node를 관리, 제어하는 PVPlayerEngine이 있다.
PVPlayerEngine을 구동하기 위한 과정

external/opencore/android/Playerdriver.cpp
PlayerDriver::startPlayerThread
1. PlayerDriver 에서 startPlayerThread를 실행
2. PlayerDriver::startPlayerThread 에서 playerThread 실행.
3. PlayerDriver::playerThread 에서 PVPlayerFactory::CreatePlayer 실행.

PlayerDriver 에서 PVPlayerFactory 가 생성되고
PVPlayerFactory 에서 PVPlayerEngine 를 생성 또는 삭제하는 API 를 제공한다.
external/opencore/engines/player/src/Pv_player_factory.cpp

PVPlayerEngine의 소스는 external/opencore/engines/player/src/Pv_player_engine.cpp 여기에 있다.
일단 PVPlayerEngine이 생성되면 (player thread가 동작 중이면)
Application은 PVPlayerEngine에서 제공하는 Command API (Command Queue로 명령을 전달하는 API)로 Source, Sink, Decode Node를 만들고(준비하고) Play, Stop, Pause등의 컨트롤을 하게 된다.

Application에서 파일 Play를 위한 기본 과정을 순서대로 정리하면 다음과 같다.

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

PVPlayerEngine::AddDataSource
PVPlayerEngine::Init
PVPlayerEngine::AddDataSink
PVPlayerEngine::Start

이런 커맨드들의 처리를 위한 함수는 DoXXX 형태로 정의되어 있으며 커맨드가 처리되었음을 알리기 위하여 EngineCommandComplete를 호출하고 필요한 경우에 내부 state를 바꾸게 된다.

(external/opencore/android/playerdriver.cpp 에도 보면 PlayerDriver::AddDataSource  PlayerDriver::Init  PlayerDriver::AddDataSink  PlayerDriver::Start 이런 이름을 갖고 있는 함수가 있는데 이것이 먼저 실행되면 PVPlayerEngine 의 것이 실행되고 그 다음에 Do~~~ 가 붙은 함수가 실행되는 것이 아닐까 추측된다.)

자 다 찍었다. 이제 돌려보도록 하자.

external/opencore/engines/player/src/Pv_player_factory.cpp 에서
*PVPlayerFactory::CreatePlayer 함수에서
return PVPlayerEngine::New(aCmdStatusObserver, aErrorEventObserver, aInfoEventObserver); => 새로운 플레이어엔진을 생성해서 리턴해줌

external/opencore/engines/player/src/Pv_player_engine.cpp 에서
PVPlayerEngine::AddCommandToQueue() 함수 실행됨. (큐에 명령어를 넣는 과정임) 인자로 넘어온 명령어를 큐에 넣음. 어디서 호출되는지 알아볼 필요 있을듯.

PVPlayerEngine::AddDataSource() 실행됨. 이 함수의 리턴은 => return AddCommandToQueue(PVP_ENGINE_COMMAND_ADD_DATA_SOURCE, (OsclAny*)aContextData, &paramvec); , PVP_ENGINE_COMMAND_ADD_DATA_SOURCE 라는 명령어를 큐에 날림.

PVPlayerEngine::AddCommandToQueue() 함수 실행 => 당연히 AddCommandToQueue를 호출했으니 실행됨 넘어온 명령어는 위와 같음

PVPlayerEngine::DoAddDataSource() 함수 실행 => 여기에 소스포맷을 체크하고 알려지지 않은 포맷을 인식하는? 그런 과정이 들어있다. 나중에 이부분 다시봐야겠다.

PVMFStatus PVPlayerEngine::DoAddDataSource(PVPlayerEngineCommand& aCmd)
{
 . . .
    // Check the source format and do a recognize if unknown
    PVMFStatus retval = PVMFSuccess;
    iSourceFormatType = iDataSource->GetDataSourceFormatType();
    if (iSourceFormatType == PVMF_MIME_FORMAT_UNKNOWN)
    {
        retval = DoQuerySourceFormatType(aCmd.GetCmdId(), aCmd.GetContext());
    }
    else
    {
        if (iSourceFormatType == PVMF_MIME_DATA_SOURCE_UNKNOWN_URL)
        {
            retval = SetupDataSourceForUnknownURLAccess();
            if (retval != PVMFSuccess)
            {
                PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerEngine::DoAddDataSource() - SetupDataSourceForUnknownURLAccess Failed"));
                return retval;
            }
        }
        // Start the source node creation and setup sequence
        retval = DoSetupSourceNode(aCmd.GetCmdId(), aCmd.GetContext());
        if (retval != PVMFSuccess)
        {
            bool ehPending = CheckForPendingErrorHandlingCmd();
            if (ehPending)
            {
                // there should be no error handling queued.
                PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_ERR, (0, "PVPlayerEngine::DoAddDataSource() Already EH pending, should never happen"));
                return PVMFPending;
            }
            // Queue up Error Handling
            PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_ERR, (0, "PVPlayerEngine::DoAddDataSource() DoSetupSourceNode failed, Add EH command"));
            iCommandCompleteStatusInErrorHandling = retval;
            iCommandCompleteErrMsgInErrorHandling = NULL;
            AddCommandToQueue(PVP_ENGINE_COMMAND_ERROR_HANDLING_ADD_DATA_SOURCE, NULL, NULL, NULL, false);
            return PVMFPending;
        }
    }
. . .
}



PVPlayerEngine::Init() 함수 실행. => 여기에서도 return AddCommandToQueue(PVP_ENGINE_COMMAND_INIT, (OsclAny*)aContextData); 이것을 리턴한다.

PVPlayerEngine::AddCommandToQueue() 함수 당연히 실행.

PVPlayerEngine::DoInit() 실행 => GetPVPlayerState()를 호출해서 현재 플레이어의 상태에 따라 초기화 할것인지 다른작업을 할것인지 결정하고 다되면 위에서 설명한대로 EngineCommandCompleted() 란 함수를 호출해서 완료되었음을 보고한다.

PVPlayerEngine::AddDataSink() 실행 => 다음은 이것이 실행된다. (내가 로그를 찍다가 일단은 걍 눈에 띄는것만 일단 넣었는데 Init가 실행된 뒤로 몇개 함수가 더 호출되었을 수도 있다.) 이것도 마지막에 AddCommandToQueue() 호출한다.

PVPlayerEngine::AddDataSink() 실행 => 두번 실행된다. 왜그럴까? 이것도 DoAddDataSink()가 실행될것이다. 로그를 안찍어놔서 지금 안나오지만.

PVPlayerEngine::Prepare() 실행 => 엔진의 상태에 따라서 상황에 맞는 동작을 하고 새로운 엔진상태를 저장한다. PVPlayerEngine::DoPrepare() 가 네번 실행된다. 이 사이에 다른 함수들도 실행된다.함수 안을 보면 왜 네번 돌아가는지 나와있다. 다음의 코드를 보자.

    if (iState == PVP_ENGINE_STATE_PREPARING)
    {
        // Engine is already in PREPARING STATE and doing Track selection. DoPrepare will be called everytime
        // engine completes a stage of track selection and flips the state to _TRACK_SELECTION_1_DONE etc.
        // If DoPrepare called without flipping the state, that means in _PREPARING state, do nothing here
        // just return.
        PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_ERR,
                        (0, "PVPlayerEngine::DoPrepare() Engine state PVP_ENGINE_STATE_PREPARING - Do Nothing"));
        return PVMFSuccess;
    }


    /* Engine will call DoPrepare 4 times
     * 1) When Engine is in PVP_ENGINE_STATE_INITIALIZED state, here engine will start the Track Selection, which
     * will start with Sink Nodes.
     * 2) After Init completes on Sink nodes Engine will be in PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE and Engine will
     * start creating Dec nodes and call Init on dec nodes, if needed.
     * 3) Init completion on Dec nodes will take Engine to PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE and Engine will
     * start populating the Playable List after verifying parameters of different tracks. Engine after selecting
     * tracks will call Reset on Sink and Dec nodes.
     * 4) Once Reset completes on Sink and Dec nodes, Engine will be in PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE and then
     * Engine will delete all the unused dec nodes.
     */

        // For the tracks which cannot be played by Sink nodes only, we need to instantiate decoder nodes.
        // Create Decoder nodes and query for the cap and config IF here.
        cmdstatus = DoDecNodeQueryCapConfigIF(aCmd.GetCmdId(), aCmd.GetContext());


PVPlayerEngine::Start() 실행 => 

PVPlayerEngine::DoStart() 실행

ALSA 작동

플레이하다가~

PVPlayerFactory::DeletePlayer() 실행되고 끝남.

여기까지는 동영상 플레이 실행 과정


OpenCore Node 등록!

OpenCore의 Node는 데이터 처리를 위한 기본 단위라고 할 수 있다.

이는 앞에서도 말했지만 OpenMax IL의 Component에 해당한다. OpenCore는 Linux에서 사용하는 OpenMAX의 Codec Component를 이용하기 위하여 Codec Node (또는 DecNode)에 OMX Component 개념을 결합시켰다.

시작하기 전에 앞서 한가지 더 얘기할 것은 Packet Video 솔루션을 개발한 사람은 Factory라는 것을 하나의 모듈을 감싸는 역활로 사용하는 것 같다. PVPlayerEngine 도 생성, 제거와 관련해서는 PVPlayerFactory로 감싸 두더니, 각각의 Node도 다시 Factory로 감싸 두고 있다. 

video나 audio decoder를 위한 Node와 Factory의 소스는 다음과 같다. 

external/opencore/nodes/pvomxvideodecnode/src/pvmf_omx_videodec_node.cpp
external/opencore/nodes/pvomxvideodecnode/src/pvmf_omx_videodec_factory.cpp

external/opencore/nodes/pvomxaudiodecnode/src/pvmf_omx_audiodec_node.cpp
external/opencore/nodes/pvomxaudiodecnode/src/pvmf_omx_audiodec_factory.cpp

 각각의 Node는 BaseDec Node로 부터 상속되었다.

external/opencore/nodes/pvomxbasedecnode/src/pvmf_omx_basedec_node.cpp

 

OpenMAX IL Componet와 연결되는 부분은 결국 BaseDec Node 부분이다. (이와 관련된 내용은 추후에 다시 설명한다) 

이러한 Node들을 사용하기 위해서는 등록 또는 해제 과정이 먼저 필요하다.
이를 위한 함수가 RegisterAllNodes()UnregisterAllNodes() 이다.

external/opencore/engines/player/config/core/Pv_player_node_registry_populator.cpp

void PVPlayerRegistryPopulator::RegisterAllNodes(PVPlayerNodeRegistryInterface* aRegistry, OsclAny*& aContext)
{
    OSCL_UNUSED_ARG(aContext);
    PVPlayerNodeInfo nodeinfo;
#if BUILD_OMX_VIDEO_DEC_NODE
    //For PVMFOMXVideoDecNode
    nodeinfo.iInputTypes.clear();
    nodeinfo.iInputTypes.push_back(PVMF_MIME_H2631998);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_H2632000);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_M4V);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_H264_VIDEO);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_H264_VIDEO_RAW);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_H264_VIDEO_MP4);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_WMV);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_REAL_VIDEO);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_DIVX);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MPEG2);
    nodeinfo.iNodeUUID = KPVMFOMXVideoDecNodeUuid;
    nodeinfo.iOutputType.clear();
    nodeinfo.iOutputType.push_back(PVMF_MIME_YUV420);
    nodeinfo.iNodeCreateFunc = PVMFOMXVideoDecNodeFactory::CreatePVMFOMXVideoDecNode;
    nodeinfo.iNodeReleaseFunc = PVMFOMXVideoDecNodeFactory::DeletePVMFOMXVideoDecNode;
    aRegistry->RegisterNode(nodeinfo);

#endif
#if BUILD_OMX_AUDIO_DEC_NODE
    //For PVMFOMXAudioDecNode
    nodeinfo.iInputTypes.clear();
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AMR_IETF);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AMR);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AMRWB_IETF);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AMRWB);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AMR_IF2);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MPEG4_AUDIO);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_3640);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_ADIF);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_ADTS);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_LATM);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_WMA);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AC3);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MP3);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_REAL_AUDIO);
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MPEG4_AUDIO_TCC); 
    nodeinfo.iInputTypes.push_back(PVMF_MIME_FLAC); 
    nodeinfo.iInputTypes.push_back(PVMF_MIME_APE); 
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MP2); 
    nodeinfo.iInputTypes.push_back(PVMF_MIME_PCM_TCC); 
    nodeinfo.iInputTypes.push_back(PVMF_MIME_MP3_TCC); 
    nodeinfo.iNodeUUID = KPVMFOMXAudioDecNodeUuid;
    nodeinfo.iOutputType.clear();
    nodeinfo.iOutputType.push_back(PVMF_MIME_PCM16);
    nodeinfo.iNodeCreateFunc = PVMFOMXAudioDecNodeFactory::CreatePVMFOMXAudioDecNode;
    nodeinfo.iNodeReleaseFunc = PVMFOMXAudioDecNodeFactory::DeletePVMFOMXAudioDecNode;

    aRegistry->RegisterNode(nodeinfo);
#endif

 

    ......
}

Node를 등록하는 과정은 위의 코드 처럼 InputTypesOutputType을 설정하고 고유 ID (UUID)를 정의하고 Create와 Delete를 위한 Callback 함수를 정의한 후, aRegistry->RegisterNode(nodeinfo)를 호출하여 등록하면 된다.

등록되어 있는것들을 나열해 보자면
//For PVMFOMXVideoDecNode
//For PVMFOMXAudioDecNode
//For PVMFVideoDecNode
//For PVMFAVCDecNode
//For PVMFWmvDecNode
//For PVMFRVDecNode
//For PVMFWmaDecNode
//For PVMFG726DecoderNode
//For PVMFGSMAMRDecNode
//For PVMFAACDecNode
//For PVMFMP3DecNode
//For PVMFRA8DecNode

//For PVMFMP4FFParserNode
//For PVMFAMRFFParserNode
//For PVMFAACFFParserNode
//For PVMFMP3FFParserNode
//For PVMFWAVFFParserNode
//For PVMFASFParserNode
//For PVMFRMFFParserNode

//For PVMFStreamingManagerNode
//For PVMFDownloadManagerNode
//For PVMFStillImageNode

//For PVMFAVIFFParserNode 다 만들고 이런식으로 끼워 넣으면 된다.

등록된 Node는 어떻게 생성될까?
이는 OpenCore 구조에서 설명한 PVPlayerEngineDoPrepare 과정을 보면 알 수 잇다.

DoPrepare 에서 DoDecNodeQueryCapConfigIF()를 호출하게 되는데 이를 보면 InputTypeOutputType에 맞는 UUID를 검색해 내서 필요한 Node를 생성하는 것을 볼 수 있다.

 external/opencore/engines/player/src/Pv_player_engine.cpp

PVMFStatus PVPlayerEngine::DoDecNodeQueryCapConfigIF(PVCommandId aCmdId, OsclAny* aCmdContext)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerEngine::DoDecNodeQueryCapConfigIF() In"));

    ......

         // Start creating decoder nodes for tracks which cannot be played with Sink nodes alone.
                  // It is also possible that there can be similar tracks with same mime strings but with
                  // different config parameters which share the same decoder node. For these similar tracks
                  // engine should not create decoder nodes again, it should use the same decoder node instance.

                    for (int k = 0; k < numParams; k++)
                    {
                        iSinkFormat = kvp[k].value.pChar_value;

                        Oscl_Vector<PVUuid, OsclMemAllocator> foundUuids;
                        // Query the player node registry for the required decoder node
                        if (iPlayerNodeRegistry.QueryRegistry(iSrcFormat, iSinkFormat, foundUuids) == PVMFSuccess)
                        {
                            if (!foundUuids.empty())
                            {
                                PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerEngine::DoDecNodeQueryCapConfigIF() Node found for %s, sink %s", currTrack->getTrackMimeType().get_str(), iSinkFormat.getMIMEStrPtr()));
                                iTrackSelectionList[i].iTsDecNode = iPlayerNodeRegistry.CreateNode(foundUuids[0]);
                            }

                        }

                    } 

 }

 
QueryRegistry()InputTypeOutputType 정보를 이용하여 등록된 Node를 검색하여 UUID를 반환한다.

external/opencore/engines/player/src/Pv_player_node_registry.cpp


PVMFStatus PVPlayerNodeRegistry::QueryRegistry(PVMFFormatType& aInputType, PVMFFormatType& aOutputType, Oscl_Vector<PVUuid, OsclMemAllocator>& aUuids)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerNodeRegistry::QueryRegistry() IN"));
    uint32 SearchCount = 0;
    bool matchfound = false;
    // Find all nodes that support the specified input and ouput format pair
    while (SearchCount < iType.size())
    {
        uint32 inputsearchcount = 0, outputsearchcount = 0;
        bool iInputFoundFlag = false, iOutputFoundFlag = false;
        while (inputsearchcount < iType[SearchCount].iInputTypes.size())
        {
            // Check if the input format matches
            if (iType[SearchCount].iInputTypes[inputsearchcount] == aInputType)
            {
                // Set the the input flag to true since we found the match in the search
                iInputFoundFlag = true;
                break;
            }
            inputsearchcount++;
        }
        //Check the flag of input format if it is true check for the output format, if not return failure
        if (iInputFoundFlag)
        {
            while (outputsearchcount < iType[SearchCount].iOutputType.size())
            {
                if (iType[SearchCount].iOutputType[outputsearchcount] == aOutputType)
                {
                    //set the the output flag to true since we found the match in the search
                    iOutputFoundFlag = true;
                    break;
                }
                outputsearchcount++;
            }
            if (iOutputFoundFlag)
            {
                // There's a match so add this node UUID to the list.
                aUuids.push_back(iType[SearchCount].iNodeUUID);
                matchfound = true;
            }
        }
        SearchCount++;
    }
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerNodeRegistry::QueryRegistry() OUT"));
    if (matchfound)
    {
        return PVMFSuccess;
    }
    else
    {
        return PVMFFailure;
    }
}

CreateNode()는 UUID를 이용하여 등록된 Node의 Create Callback 함수를 호출한다.

PVMFNodeInterface* PVPlayerNodeRegistry::CreateNode(PVUuid& aUuid)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerNodeRegistry::CreateNode() IN"));
    bool iFoundFlag = false;
    uint32 NodeSearchCount = 0;

    while (NodeSearchCount < iType.size())
    {
        //Search if the UUID's will match
        if (iType[NodeSearchCount].iNodeUUID == aUuid)
        {
            //Since the UUID's match set the flag to true
            iFoundFlag = true;
            break;
        }

        NodeSearchCount++;

    }

    if (iFoundFlag)
    {
        OsclActiveObject::OsclActivePriority priority = OsclActiveObject::EPriorityNominal;
        PVPlayerNodeInfo* nodeInfo = &iType[NodeSearchCount];
        PVMFNodeInterface* nodeInterface = NULL;

#if VIDEO_DEC_NODE_LOW_PRIORITY
        //Call the appropriate Node creation function & return Node pointer
        if (KPVMFOMXVideoDecNodeUuid == aUuid)
        {
            priority = OsclActiveObject::EPriorityLow;
        }
#endif
        if (NULL != nodeInfo->iNodeCreateFunc)
        {
            nodeInterface = (*(iType[NodeSearchCount].iNodeCreateFunc))(priority);
        }

        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerNodeRegistry::CreateNode() OUT"));
        return nodeInterface;
    }
    else
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerNodeRegistry::CreateNode() OUT"));
        return NULL;
    }




OpenCore Recognizer 등록

OpenCore에서 Recognizer는 입력 소스로부터 필요한 Parser NodeDecode Node 등을 선택할 수 있도록 해준다.
(그렇게 생각 중인데 코드를 보니 FormatType를 결정하게 해주기는 하는데 이를 어떻게 이용하는지에 대해서는 조금 더 봐야 겠다.)

Node를 등록할 때 RegisterAllNodes()UnregisterAllNodes() 를 사용한 것 처럼
RegisterAllRecognizers()UnregisterAllRecognizers()로 Recognizer를 등록/해제할 수 있다.

external/opencore/engines/player/config/core/Pv_player_node_registry_populator.cpp (위에 Node 등록한 파일이랑 Reconizer랑 같은 파일)

void PVPlayerRegistryPopulator::RegisterAllRecognizers(PVPlayerRecognizerRegistryInterface* aRegistry, OsclAny*& aContext)
{
    //Keep a list of all factories allocated by this populator for later cleanup.
    typedef Oscl_Vector<PVMFRecognizerPluginFactory*, OsclMemAllocator> nodelistType;
    nodelistType* nodeList = OSCL_NEW(nodelistType, ());
    aContext = nodeList;

    PVMFRecognizerPluginFactory* tmpfac = NULL;

#if BUILD_MP4_FF_REC
    tmpfac = OSCL_STATIC_CAST(PVMFRecognizerPluginFactory*, OSCL_NEW(PVMP4FFRecognizerFactory, ()));
    if (PVMFRecognizerRegistry::RegisterPlugin(*tmpfac) == PVMFSuccess)
    {
        aRegistry->RegisterRecognizer(tmpfac);
        nodeList->push_back(tmpfac);
    }
    else
    {
        OSCL_DELETE(((PVMP4FFRecognizerFactory*)tmpfac));
        tmpfac = NULL;
        return;
    }
#endif

    ......

}

#if BUILD_MP4_FF_REC
#if BUILD_ASF_FF_REC
#if BUILD_OMA1_FF_REC
#if BUILD_AAC_FF_REC
#if BUILD_RM_FF_REC
//#if BUILD_AVI_FF_REC 이렇게 넣으면 된다.
#if BUILD_MP3_FF_REC
#if BUILD_MP3_FF_REC
#if BUILD_AMR_FF_REC

이러한 Recognizer들도 Node와 마찬가지로 RecognizerPlugIn과 해당 Factory로 이루어져 있다.
Mp4 를 예로 들면
external/opencore/pvmi/recognizer/plugin/pvmp4ffrecognizer/src/Pvmp4ffrec_factory.cpp

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

 

////////////////////////////////////////////////////////////////////////////
OSCL_EXPORT_REF void PVMP4FFRecognizerFactory::DestroyRecognizerPlugin(PVMFRecognizerPluginInterface* aPlugin)
{
    if (aPlugin)
    {
        OSCL_DELETE(((PVMP4FFRecognizerPlugin*)aPlugin));
    }
}


external/opencore/pvil/recognizer/plugin/pvmp4ffrecognizer/src/Pvmp4ffrec_plugin.cpp

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

    // Check if the specified file is MP4 file by using MP4 parser lib's static function
    bool ismp4file = false;
    OSCL_wHeapString<OsclMemAllocator> tmpfilename;
    int32 leavecode = 0;
    OSCL_TRY(leavecode, ismp4file = MP4FileRecognizer::IsMP4File(&aSourceDataStreamFactory));
    OSCL_FIRST_CATCH_ANY(leavecode,
                         return PVMFErrNoMemory;
                        );

 

    if (ismp4file == true)
    {
        // It is an MP4 file so add positive result
        PVMFRecognizerResult result;
        result.iRecognizedFormat = PVMF_MIME_MPEG4FF;
        result.iRecognitionConfidence = PVMFRecognizerConfidenceCertain;
        aRecognizerResult.push_back(result);

    }

 

    return PVMFSuccess;
}

즉, Recognizer Factory를 등록해서 나중에 필요한 경우에 PVMP4FFRecognizerPlugin::Recognize()를 호출해서 mp4 파일인지 아닌지를 확인하도록 한다. 그럼 Recognizer()는 실제로 언제 호출될까?

 Application에서 파일 Play를 위한 기본 과정은 다음과 같다고 했는데
1. AddDataSource()
2. Init()
3. AddDataSink() : video 출력용
4. AddDataSink() : audio 출력용
5. Prepare()
6. Start()
7. Stop()

AddDataSource()를 호출하면 PVPlayerEngine은 DoAddDataSource()를 호출하게 된다.
engines/player/src/pv_player_engine.cpp


PVMFStatus PVPlayerEngine::DoAddDataSource(PVPlayerEngineCommand& aCmd)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, iPerfLogger, PVLOGMSG_NOTICE,
                    (0, "PVPlayerEngine::DoAddDataSource() Tick=%d", OsclTickCount::TickCount()));

     ......

     // Check the source format and do a recognize if unknown
    PVMFStatus retval = PVMFSuccess;
    iSourceFormatType = iDataSource->GetDataSourceFormatType();

    if (iSourceFormatType == PVMF_MIME_FORMAT_UNKNOWN)
    {
        retval = DoQuerySourceFormatType(aCmd.GetCmdId(), aCmd.GetContext());
    }
    ...... 

}


DoAddDataSource()에서는 DoQuerySourceFormatType()를 호출하고
engines/player/src/pv_player_engine.cpp

PVMFStatus PVPlayerEngine::DoQuerySourceFormatType(PVCommandId aCmdId, OsclAny* aCmdContext)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, iPerfLogger, PVLOGMSG_INFO,
                    (0, "PVPlayerEngine::DoQuerySourceFormatType() Tick=%d", OsclTickCount::TickCount()));

     ......

    if (pvInterface != NULL && pvInterface->queryInterface(SourceContextDataUuid, SourceContextData))
    {
        PVMFSourceContextData * aSourceContextData = OSCL_STATIC_CAST(PVMFSourceContextData*, SourceContextData);
        PVMFSourceContextDataCommon * aSourceContextDataCommon = aSourceContextData->CommonData();
        if (aSourceContextDataCommon)
        {
            DataStreamDataFactory = aSourceContextDataCommon->iRecognizerDataStreamFactory;
        }
    }

      if (DataStreamDataFactory)
    {
        OSCL_TRY(leavecode, retval = iPlayerRecognizerRegistry.QueryFormatType(DataStreamDataFactory, *this, (OsclAny*) context));
        OSCL_FIRST_CATCH_ANY(leavecode,
                             retval = PVMFErrNotSupported;
                            );
    }


결국 RecognizerRegistry에서 FormatType을 Query 하는 곳으로 갈 수 밖에 없다. 조건분기 뭐 이런게 아니라 순서상 저기로 간다.

external/opencore/engines/player/src/Pv_player_node_registry.cpp

PVMFStatus PVPlayerRecognizerRegistry::QueryFormatType(PVMFCPMPluginAccessInterfaceFactory* aDataStreamFactory, PVPlayerRecognizerRegistryObserver& aObserver, OsclAny* aCmdContext)
{
    PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVPlayerRecognizerRegistry::QueryFormatType() IN"));

     ......

      // Open the session with recognizer
    PVMFRecognizerRegistry::OpenSession(iRecSessionId, *this);

    // Request file recognition
    iRecognizerResult.clear();
    iRecognizeCmdId = PVMFRecognizerRegistry::Recognize(iRecSessionId, *iDataStreamFactory, NULL, iRecognizerResult, NULL);

}

external/opencore/pvmi/recognizer/src/Pvmf_recognizer_registry.cpp

OSCL_EXPORT_REF PVMFCommandId PVMFRecognizerRegistry::Recognize(PVMFSessionId aSessionId, PVMFDataStreamFactory& aSourceDataStreamFactory, PVMFRecognizerMIMEStringList* aFormatHint,
        Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult, OsclAny* aCmdContext, uint32 aTimeout)
{
    PVMFRecognizerRegistryImpl* pvrecregimpl = OSCL_STATIC_CAST(PVMFRecognizerRegistryImpl*, PVMFRECOGNIZER_REGISTRY::getInstance(PVMFRECOGNIZER_REGISTRY_ID));


    if (pvrecregimpl != NULL)
    {
        return pvrecregimpl->Recognize(aSessionId, aSourceDataStreamFactory, aFormatHint, aRecognizerResult, aCmdContext, aTimeout);
    }
    else
    {
        // Registry hasn't been initialized yet. Assert
        OSCL_ASSERT(false);
        OSCL_LEAVE(OsclErrNotReady);
        return 0;
    }
}

external/opencore/pvmi/recognizer/src/Pvmf_recognizer_impl.cpp


PVMFCommandId PVMFRecognizerRegistryImpl::Recognize(PVMFSessionId aSessionId, PVMFDataStreamFactory& aSourceDataStreamFactory, PVMFRecognizerMIMEStringList* aFormatHint,
        Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult, OsclAny* aCmdContext, uint32 aTimeout)
{
    ......
     // Add the command to the pending list
    return AddRecRegCommand(aSessionId, PVMFRECREG_COMMAND_RECOGNIZE, aCmdContext, ¶mvector, true);




void PVMFRecognizerRegistryImpl::Run()
{
    int32 leavecode = 0;

     ......

          PVMFStatus cmdstatus = PVMFSuccess;
        switch (cmd.GetCmdType())
        {
            case PVMFRECREG_COMMAND_RECOGNIZE:
                DoRecognize();
                break;

            case PVMFRECREG_COMMAND_CANCELCOMMAND:
                // CancelCommand() should not be handled here
                OSCL_ASSERT(false);
                // Just handle as "not supported"
                cmdstatus = PVMFErrNotSupported;
                break;

            default:
                cmdstatus = PVMFErrNotSupported;
                break;
        }

}

void PVMFRecognizerRegistryImpl::DoRecognize()
{

    ......

         for (uint32 i = 0; i < iRecognizerPluginFactoryList.size(); ++i)
        {
            // Create the recognizer plugin
            PVMFRecognizerPluginInterface* recplugin =
                CreateRecognizerPlugin(*(iRecognizerPluginFactoryList[i]));

            if (recplugin)
            {
                LOGINFO((0, "PVMFRecognizerRegistryImpl::DoRecognize Calling recognizer i=%d", i));
                uint32 currticks = OsclTickCount::TickCount();
                uint32 starttime = OsclTickCount::TicksToMsec(currticks);
                OSCL_UNUSED_ARG(starttime);

                // Perform recognition with this recognizer plug-ing
                recplugin->Recognize(*iDataStreamFactory, hintlist, *recresult);
                // Done with this recognizer so release it

                currticks = OsclTickCount::TickCount();
                uint32 endtime = OsclTickCount::TicksToMsec(currticks);
                OSCL_UNUSED_ARG(endtime);

                DestroyRecognizerPlugin(*(iRecognizerPluginFactoryList[i]), recplugin);

                if (!recresult->empty())
                {
                    LOGINFO((0, "PVMFRecognizerRegistryImpl::DoRecognize Out of recognizer i=%d  result=%d, time=%d",
                             i, (recresult->back()).iRecognitionConfidence, (endtime - starttime)));
                    //LOGINFO((0,"PVMFRecognizerRegistryImpl::DoRecognize out of recognizer i=%d  result=%d, time=%d, mime=%s",
                    //i,(recresult->back()).iRecognitionConfidence, (endtime-starttime), (recresult->back()).iRecognizedFormat.get_cstr()));
                    if ((recresult->back()).iRecognitionConfidence == PVMFRecognizerConfidenceCertain)
                    {
                        CompleteCurrentRecRegCommand(PVMFSuccess);
                        return;
                    }
                }

            }

        }

}

결국 연결되고 연결되어서 다시 PVMFRECREG_COMMAND_RECOGNIZE 커맨드를 보내게 되고 이를 Run()에서 받아서 DoRecognize()를 호출해서 처리하게 된다.

 이러한 처리 과정의 결과는 PVPlayerRecognizerRegistry 클래스의 iSourceFormatType에 저장된다고 생각되는데 이를 어떻게 이용하는지에 대해서는 명확하지 않다. (코드가 하도 뱅글 뱅글 돌아서 언제인가는 이해하겠지...)

정리하면 AddDataSource() 를 실행하면 결국엔 Recognize 함수까지 오는데  여기에서
Recognize 함수는 pvmi/recognizer/include/Pvmf_recofnizer_plugin.h 파일에

class PVMFRecognizerPluginInterface
{
    public:
        /**
         * Virtual destructor for the plug-in. All plug-ins should perform any clean up here
         **/
        virtual ~PVMFRecognizerPluginInterface()
        {
        };
. . . .  ( 중략 )
        virtual PVMFStatus Recognize(PVMFDataStreamFactory& aSourceDataStreamFactory,
                                     PVMFRecognizerMIMEStringList* aFormatHint,
                                     Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult) = 0;
. . . . (후략)

이렇게 되어 있고
이 PVMFRecognizerPluginInterface 클래스는
pvmi/recognizer/plugins/pvmp4ffrecognizer/src/pvmp4ffrec_plugin.h 에서 아래와 같이 상속된다. 그리고 하단에 보면 Recognize 가

class PVMP4FFRecognizerPlugin : public PVMFRecognizerPluginInterface
{
    public:
        PVMP4FFRecognizerPlugin()
        {
        };
        ~PVMP4FFRecognizerPlugin()
        {
        };
        PVMFStatus SupportedFormats(PVMFRecognizerMIMEStringList& aSupportedFormatsList);
        PVMFStatus Recognize(PVMFDataStreamFactory& aSourceDataStreamFactory, PVMFRecognizerMIMEStringList* aFormatHint,
                             Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult);

이번 문단의 맨 처음의 호출된 함수 Recognize 로 가게 된다.

external/opencore/pvil/recognizer/plugin/pvmp4ffrecognizer/src/Pvmp4ffrec_plugin.cpp (위에 써있는데 이해를 위해 다시 썼다.)

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

    // Check if the specified file is MP4 file by using MP4 parser lib's static function
    bool ismp4file = false;
    OSCL_wHeapString<OsclMemAllocator> tmpfilename;
    int32 leavecode = 0;
    OSCL_TRY(leavecode, ismp4file = MP4FileRecognizer::IsMP4File(&aSourceDataStreamFactory));
    OSCL_FIRST_CATCH_ANY(leavecode,
                         return PVMFErrNoMemory;
                        );

 

    if (ismp4file == true)
    {
        // It is an MP4 file so add positive result
        PVMFRecognizerResult result;
        result.iRecognizedFormat = PVMF_MIME_MPEG4FF;
        result.iRecognitionConfidence = PVMFRecognizerConfidenceCertain;
        aRecognizerResult.push_back(result);

    }

 

    return PVMFSuccess;
}

즉, AddDataSource() 를 하면 for (uint32 i = 0; i < iRecognizerPluginFactoryList.size(); ++i) 이런식으로 등록되어 있는 플러그인들이 뺑뺑이를 돌면서 등록되어 있는 포맷이랑 맞는지 확인하고 다음과정인 init()로 넘어가게 되는것 같다.


OpenCore에서 Prepare 과정

Application이 파일 Play를 하기 위한 과정 중 가장 중요한 과정은 Prepare() 일 것이다. 

AddDataSource는 파일의 포맷을 알아내고
AddDataSink는 실제로 Video 또는 Audio 출력 포트에 대한 정보를 저장해 두는 역활 정도만 한다.

각각의 Sink Node, Decode Node, Parser(Source) Node를 생성하고 연결하는 모든 작업은 Prepare()에서 이루어진다.
Prepare()를 호출하면 결국 PVPlayerEngine에서는 DoPrepare()가 호출되게 된다.
DoPrepare()를 보면 내부 state를 관리하여 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를 생성하고 연결하게 된다. 

1. PVP_ENGINE_STATE_INITIALIZED

1) DoSinkNodeQueryCapConfigIF() 호출 

PVMFStatus PVPlayerEngine::DoSinkNodeQueryCapConfigIF(PVCommandId aCmdId, OsclAny* aCmdContext)
{
    ......
          // Query for Cap-Config IF
        context = AllocateEngineContext(&iDatapathList[i], iDatapathList[i].iSinkNode, NULL, aCmdId, aCmdContext, PVP_CMD_SinkNodeQueryCapConfigIF);

        PVUuid capconfiguuid = PVMI_CAPABILITY_AND_CONFIG_PVUUID;
        cmdid = -1;
        leavecode = 0;
        iDatapathList[i].iSinkNodePVInterfaceCapConfig = NULL;
        leavecode = IssueQueryInterface(iDatapathList[i].iSinkNode, iDatapathList[i].iSinkNodeSessionId, capconfiguuid, iDatapathList[i].iSinkNodePVInterfaceCapConfig, (OsclAny*)context, cmdid);
    ......
}

1-1) NodeCommandCompleted()에서 HandleSinkNodeQueryCapConfigIF() 호출하고 여기에서 DoSinkNodeInit() 호출하는데 결국 DoSinkNodeInit()에 의해서 SinkNode 갯수만큼 PVP_CMD_SinkNodeInit 인자를 가지고 AllocateEngineContext()를 호출하게 된다.

void PVPlayerEngine::HandleSinkNodeQueryCapConfigIF(PVPlayerEngineContext& aNodeContext, const PVMFCmdResp& aNodeResp)
{
     ...... 

     // Decrement the pending counter and go to next step if 0.
    --iNumPendingNodeCmd;
    if (iNumPendingNodeCmd == 0)
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,
                        (0, "PVPlayerEngine::HandleSinkNodeQueryCapConfigIF() All QueryInterface() commands complete"));

         PVMFStatus cmdstatus = DoSinkNodeInit(aNodeContext.iCmdId, aNodeContext.iCmdContext);

      ......
}

2. PVP_ENGINE_STATE_TRACK_SELECTION_1_DONE

1) DoSinkNodeTrackSelection()
2) DoDecNodeQueryCapConfigIF()
    2-1) DoDecNodeInit() 

이부분은 함수명으로 이해가 될 것이고 생각되는데 전에 설명했던 Decode Node를 생성하는 역활을 하게 된다. 

 3. PVP_ENGINE_STATE_TRACK_SELECTION_2_DONE

1) DoSourceNodeTrackSelection()
    1-1) DoTrackSelection()
2) DoSinkNodeDecNodeReset()
Track 관련 설정인데 이 부분을 하나의 State로 분리한지는 잘 모르겠다. 

4. PVP_ENGINE_STATE_TRACK_SELECTION_3_DONE 

1) DoSinkDecCleanupSourcePrepare()
    1-1) DoSourceNodePrepare()
          1-1-1) DoSinkNodeQueryInterfaceOptional()
                    1-1-1-1) DoDatapathPrepare()
결국 DoSourceNodePrepare()를 호출하여 Parser Node를 생성하게 된다.

 

사실 코드를 보고 있으면 어떤 목적인지는 모르겠지만 모든 것이 Commnad를 다시 보내서 해당 Callback이 호출되는 구조이고 계속적으로 함수콜이 연결되어서 분석 자체가 쉽지 않다.

단 Interface를 이용하는 일관적인 구조와 Command와 Context를 Queue로 관리하는 부분 정도는 처음 설계하던 사람의 의도만 파악이 되면 좀 더 쉽게 분석이 될 것 같다는 생각을 하게 만든다.


pv_mkvfile_parser_utils.cpp => chunk 읽는 함수가 있다.
pv_mkvfile_stramlist.cpp => chunk 별로 읽어서 처리하는 함수가 있다.
pv_mkvfile_typedefs.h => FourCC 헤더, bitmap 헤더, WAVE 파일 헤더, 기타 잡것들 들어있다.
pv_mkvfile.h => MKV 파일 헤더 읽기, MKV 파일 확인 함수 이런것들이 가상함수로 정의되어 있다.

external\opencore\fileformats\mkv\parser\src 에  pv_mkvfile.cpp 파일에는 PVMkvFile::IsMkvFile() 라는 함수가 있는데
이 함수 안에 pMkvFileParser->IsMKVFile(); 이런 부분이 있다. (파서에 한번 넣어서 파일을 확인 하는 과정이다.)

이것은 PVMkvFileParser 라는 클래스에 있는 파일 구분 함수이다. 이 클래스는
fileformats/mkv/parser/include/Pvmkvfile_parser.h 에있고 여기서 보면 

//Verifies if the supplied file is valid MKV file
 PV_MKV_FILE_PARSER_ERROR_TYPE IsMKVFile(); 로 선언되어 있다.

fileformats/mkv/parser/include/Pvmkvfile_parser.cpp 에서 어떻게 구현되어 있는지 확인해보자.

PV_MKV_FILE_PARSER_ERROR_TYPE
PVMkvFileParser::IsMKVFile()
{
    bool IsFileBroken = false;  //파일깨졌는지 안깨졌는지 넣는 것도 보이고....
    iError = PV_MKV_FILE_PARSER_SUCCESS;
( . . . 중략 )
//파일 사이즈 체크하는 루틴도 들어있다.


    if ((iError = PVMkvFileParserUtils::ReadNextChunkType(ipFilePtr, chunkType)) != PV_MKV_FILE_PARSER_SUCCESS)
    {
        PVMKVFILE_LOGERROR((0, "PVMkvFileParser::IsMKVFile: UnSupported Chunk at begining of the file"));
        return iError;
    }
    //first chunk must be RIFF
    if (chunkType != RIFF)
    {
        PVMKVFILE_LOGERROR((0, "PVMkvFileParser::IsMKVFile: First Chunk Must be RIFF"));
        iError = PV_MKV_FILE_PARSER_WRONG_FILE;
        return iError;
    }

//파일 사이즈를 계산해서 파일이 깨졌는지 안깨졌는지 확인하는 루틴도 있음

기타등등 루틴들....
(.... 이하 생략)

위의 함수 내부에서 동영상 파일을 읽어내는 함수들은 결과값으로 에러타입을 리턴하게 되어있는데
리턴되는 에러타입은 다음과 같이 enum 형으로 정의되어 있다.
fileformats/mkv/parser/include/Pv_avifile_status.h 에


typedef enum
{
    PV_AVI_FILE_PARSER_ERROR_UNKNOWN             = -1,
    PV_AVI_FILE_PARSER_SUCCESS                   = 0,
    PV_AVI_FILE_PARSER_FILE_OPEN_ERROR           = 1,
    PV_AVI_FILE_PARSER_INSUFFICIENT_MEMORY       = 2,
    PV_AVI_FILE_PARSER_WRONG_CHUNK               = 3,
    PV_AVI_FILE_PARSER_WRONG_CHUNK_SIZE          = 4,
    PV_AVI_FILE_PARSER_WRONG_FILE                = 5,
    PV_AVI_FILE_PARSER_WRONG_SIZE                = 6,
    PV_AVI_FILE_PARSER_READ_ERROR                = 7,
    PV_AVI_FILE_PARSER_ERROR_NUM_STREAM          = 8,
    PV_AVI_FILE_PARSER_ERROR_STREAM_TYPE_UNKNOWN = 9,
    PV_AVI_FILE_PARSER_NO_INDEX_CHUNK            = 10,
    PV_AVI_FILE_PARSER_EOF_REACHED               = 11,  //End of file
    PV_AVI_FILE_PARSER_EOS_REACHED               = 12,  //End of stream
    PV_AVI_FILE_PARSER_USE_INDX_TBL              = 13,
    PV_AVI_FILE_PARSER_BYTE_COUNT_ERROR          = 14,
    PV_AVI_FILE_PARSER_UNSUPPORTED_CHUNK         = 15,
    PV_AVI_FILE_PARSER_ERROR_WRONG_STREAM_NUM    = 16,
    PV_AVI_FILE_PARSER_WRONG_OFFSET              = 17,
    PV_AVI_FILE_PARSER_NO_OFFSET_FOUND           = 18,
    PV_AVI_FILE_PARSER_WRONG_BIT_COUNT           = 19,
    PV_AVI_FILE_PARSER_SEEK_ERROR                = 20
}PV_AVI_FILE_PARSER_ERROR_TYPE;

아하! 동영상 돌리다가 파서에서 에러나면서 숫자 찍히는게 이거였던 것이로구나....















 


sp<SurfaceComposerClient> videoClient = new SurfaceComposerClient; 

SurfaceComposerClient는  SurfaceFlinger 에 의해 실행된다.
frameworks/base/libs/surfaceflinger/

The current  calling processing for create an Surface:


SurfaceComposerClient::createSurface       (ISurface → Suface)
→ (IsurfaceFlingerClient:: createSurface)
→  Bclient::createSurface                          (It is in SurfaceFlinger code)
→  SurfaceFlinger::createSurface               (Then create the true Surface)

LayerBaseClient* layer = 0;
sp<LayerBaseClient::Surface> surfaceHandle;
        eFXSurfaceNormal → Layer or LayerBuffer
        eFXSurfaceBlur → LayerBlur
        eFXSurfaceDim → LayerDim
surfaceHandle = layer->getSurface();


SurfaceComposerClient 는 Surface JNI code에서 사용하게 되어있다.
frameworks/base/core/jni/android_view_Surface.cpp 여길 참고하면 된다.













.
Posted by Real_G