Monday, October 26, 2009

Using Android MediaPlayer framework to plug my player extension

Emboldened by using ISurface in the native code, I set out to follow Android MediaPlayer architecture and write my own media player (does nothing, renders same as the earlier one, visit this to understand how rendering works). Android MediaPlayer architecture is plugable architecture but still one needs to recompile Media libraries, I wonder why Google did not make this runtime plugable like Netscape plug-in.

There are 2 major tasks to plug our Media player into Android architecture,  Mediaplayer service creates appropriate instance of a media player based on the data source, in our case the extension “avi”. Since avi is not supported, I choose to add this as myPlayer support format.

1. We need to extend MediaPlayerInterface, defined in  media/MediaPlayerInterface.h
2. Insert our player instance at the appropriate place in  MediaPlayerService.cpp

Below is the interface  myPlayer I derived from MediaPlayerInterface. I have used Vorbis implementation as base to get this up and running in a day ( well .. spent a week on thinking how to..). I have added my understanding as comments in the code below

class myPlayer : public MediaPlayerInterface {
public:
    myPlayer();

    virtual             ~myPlayer();
    virtual void        onFirstRef();
    virtual status_t    initCheck(); //return player status 
    virtual status_t    setAudioStreamType(int type);
    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& 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    getVideoWidth(int *w);
    virtual status_t    getVideoHeight(int *h);
    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 MY_PLAYER; }


//myPlayer methods //---------------------------------------------------------------------------------------

    static  int         renderThread(void*);
    int render();

//-----------------------------------------------------------------------------------------------------------

private:

    bool                    mIsDataSourceSet;
    sp                       mSurface;
    status_t             mStatus;
    int                       mDuration;
    int                       mStreamType;
    Mutex                 mMutex;
    Condition           mCondition;
    pid_t                   mRenderTid;
    volatile bool       mExit;
    bool                     mPaused;
    volatile bool       mRender;


//myPlayer stuff for rendering //------------------------------------------------------------------------------------------//
    sp mFrameheap;
    int mHorstride;
    int mVerstride;
    int mWidth;
    int mHeight;
    int mFrameoffset[2];
    int mBufindex;
    short *mFramebuf;
    PixelFormat mPixformat;
//myPlayer stuff //------------------------------------------------------------------------------------------//

}; //class myPlayer

 In the  MediaPlayerService.cpp add .avi into FILE_EXTS. This will be used in getPlayerType (...) method

extmap FILE_EXTS [] =  {
        {".mid", SONIVOX_PLAYER},
        {".midi", SONIVOX_PLAYER},
        {".smf", SONIVOX_PLAYER},
        {".xmf", SONIVOX_PLAYER},
        {".imy", SONIVOX_PLAYER},
        {".rtttl", SONIVOX_PLAYER},
        {".rtx", SONIVOX_PLAYER},
        {".ota", SONIVOX_PLAYER},
        {".ogg", VORBIS_PLAYER},
        {".oga", VORBIS_PLAYER},
        {".avi", MY_PLAYER},
};

Now add myPlayer instance in createPlayer. We are done. Not to forget to add and libmyplayer to MediaService’s Android.mk file.


static sp createPlayer(player_type playerType, void* cookie, notify_callback_f notifyFunc){
 .
 .
 .
         case MY_PLAYER:
            LOGE(" create myPlayer");
            p = new myPlayer();
            break;
 .
 .
 .
}

Now, lets get back to myPlayer.cpp implementation.  Lets pay attention only to the following methods, others are just dummy returns No_ERROR. mStatus plays key role here as you can see it changes from method to method.

myPlayer, constructor initializes class variables, here esp, I have set screen size to 320X430. In reality this has to handled at runtime.
    myPlayer();
Destructor clears up Surface buffers and frees up thread
    virtual             ~myPlayer();
Create the render thread and wait for signal, the signal is sent out from start() method.
    virtual void        onFirstRef();
InitCheck does nothing just pass the mStatus
    virtual status_t    initCheck();
Set the file URL, here we do nothing but this is the place one can open the file and check it’s valid, do parsing and return OK
    virtual status_t    setDataSource(const char *url);
    virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
Important method, we store the Surface handle and use it for rendering. Also, register our buffers
    virtual status_t    setVideoSurface(const sp& surface);
Called from Java application class MediaPlayer, may be we can prepare CODECs here or initialize e double buffering etc. Now does nothing
    virtual status_t    prepare();
    virtual status_t    prepareAsync();
Called from Java application class MediaPlayer, this is where we send single() to render thread to start
    virtual status_t    start();

Rendering is done through double buffer,

//myPlayer methods  //---------------------------------------------------------------------------------------

    static  int         renderThread(void*);
    int render();

//-----------------------------------------------------------------------------------------------------------



#include "myPlayer.h"

#ifdef HAVE_GETTID
static pid_t myTid() { return gettid(); }
#else
static pid_t myTid() { return getpid(); }
#endif


/* max frame buffers */
#define MAX_FRAME_BUFFERS     2

#define WIDTH  320
#define HEIGHT  430

namespace android {


static status_t ERROR_NOT_OPEN = -1;
static status_t ERROR_OPEN_FAILED = -2;
static status_t ERROR_ALLOCATE_FAILED = -4;
static status_t ERROR_NOT_SUPPORTED = -8;
static status_t ERROR_NOT_READY = -16;
static status_t STATE_INIT = 0;
static status_t STATE_ERROR = 1;
static status_t STATE_OPEN = 2;

// ----------------------------------------------------------------------------------------------------------------
myPlayer::myPlayer(){
    LOGI ("myPlayer Constructor");
    mStatus = STATE_ERROR;
    mDuration = -1;
    mSurface = NULL;
    mRenderTid = 0;
   mExit = false;
   mPaused = false;
   mRender = false;


//----------------------------------------------------
//myPlayer stuff initialize

    mWidth = (WIDTH + 1) & -2;;
    mHeight = (HEIGHT + 1) & -2;;

    mHorstride = mWidth;
    mVerstride = mHeight;

   int frameSize = mWidth*mHeight*2;
   /* create frame buffer heap base */
 mFrameheap = new MemoryHeapBase(frameSize * MAX_FRAME_BUFFERS);
    if (mFrameheap->heapID() < 0) {
        LOGI("Error creating frame buffer heap!");
    }

    for ( int i = 0; i<MAX_FRAME_BUFFERS; i++){
     mFrameoffset[i] = i*frameSize;
    }

 mBufindex = 0;

 mFramebuf = new short[mWidth*mHeight];

    mPixformat = PIXEL_FORMAT_RGB_565;

}

// ----------------------------------------------------------------------------------------------------------------
void myPlayer::onFirstRef()
{
    LOGV("onFirstRef");
    // create playback thread
    Mutex::Autolock l(mMutex);
    createThreadEtc(renderThread, this, "vorbis decoder");
    mCondition.wait(mMutex);
    if (mRenderTid > 0) {
        LOGV("render thread(%d) started", mRenderTid);
        mStatus = STATE_INIT;
    }
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::initCheck(){
    LOGI ("mStatus = %s\n", (mStatus==NO_ERROR)?"NO_ERROR":"ERROR");
    if (mStatus != STATE_ERROR) return NO_ERROR;
    return ERROR_NOT_READY;
}

// ----------------------------------------------------------------------------------------------------------------

myPlayer::~myPlayer(){

    LOGI ("myPlayer destruct");

 mExit=true;
 mRender=false;

    if (mSurface != NULL){
  mSurface->unregisterBuffers();
        mSurface.clear();
    }

 if(mFramebuf)
  free(mFramebuf);

 mFrameheap.clear();

 mFramebuf = NULL;

}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::setDataSource(const char *url){
  
 //now nothing done, here you may add file stream code and paring the file
    LOGI("setDataSource: url = %s\n", url);

 if (url) {
  mStatus = STATE_OPEN;
  mExit=false;
 }
 else {
  mStatus = ERROR_NOT_OPEN;
 }
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::setDataSource(int fd, int64_t offset, int64_t length){

    LOGI("fd: %i, offset: %ld, len: %ld\n", fd, (long)offset, (long)length);

    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::setVideoSurface(const sp& surface){
    LOGI("ISurface: %p\n", surface.get());

    mSurface = surface;
    /* create frame buffer heap and register with surfaceflinger */
    ISurface::BufferHeap buffers( mWidth, mHeight, mHorstride, mVerstride, mPixformat, mFrameheap);

    if (mSurface->registerBuffers(buffers) < 0 ){
        LOGI("Cannot register frame buffer!");
        mFrameheap.clear();
        return STATE_ERROR;
    }

 mRender = true;
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::prepare(){
 LOGI("prepare");
 mStatus = STATE_OPEN;
 mRender = false;
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::prepareAsync(){

    LOGI("prepareAsync\n");

 if (mStatus != STATE_OPEN ) {
        sendEvent(MEDIA_ERROR);
        return NO_ERROR;
    }
    sendEvent(MEDIA_PREPARED);

    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::start(){
 LOGI("start");
    Mutex::Autolock l(mMutex);
    if (mStatus != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }
 mRender = true;
    mCondition.signal();
    return NO_ERROR;
}
// ----------------------------------------------------------------------------------------------------------------
int myPlayer::renderThread(void* p) {
 LOGI("renderThread");
    return ((myPlayer*)p)->render();
}

// ----------------------------------------------------------------------------------------------------------------

int myPlayer::render(){
 LOGI("render Enter");
 {
  Mutex::Autolock l(mMutex);
  mRenderTid = myTid();
  mCondition.signal();
 }

 int frame = 0;

  // nothing to render, wait for client thread to wake us up
 while (1) {
 
  if (!mExit && !mRender) {
   Mutex::Autolock l(mMutex);
   LOGI("render - signal wait\n");
   mCondition.wait(mMutex);
   LOGI("render - signal rx'd\n");
  }


/// ------//  myPlayer stuff -- replace with decoders //--------//
  if (++mBufindex == MAX_FRAME_BUFFERS)
   mBufindex = 0;
 
  for (int i=0; i< mWidth*mHeight; i++){
   mFramebuf[i] = (short)(i+frame*16) ;
  }
  memcpy (static_cast<unsigned char *>(mFrameheap->base()) + mFrameoffset[mBufindex],  mFramebuf, mWidth*mHeight*sizeof(short));
  mSurface->postBuffer(mFrameoffset[mBufindex]);
  if(++frame == 430)
   frame=0;
 
/// ------// myPlayer stuff end-----------------------------------//

  if (mExit) break;
 }

 {
  Mutex::Autolock l(mMutex);
  mRenderTid = -1;
  mCondition.signal();
 }

 LOGI("render Exit");
 return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------

status_t myPlayer::setAudioStreamType(int type){
    mStreamType = type;
    LOGI ("mStreamType = %d\n", mStreamType);
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::stop(){
 LOGI("stop");
 mRender = false;
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::pause(){
 LOGI("stop");
 mRender = false;
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
bool myPlayer::isPlaying(){
 LOGI("isPlaying");

 return mRender;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::getVideoWidth(int *w){
 LOGI("getVideoWidth");
 *w = 320;
    return NO_ERROR;

}
// ----------------------------------------------------------------------------------------------------------------

status_t myPlayer::getVideoHeight(int *h){
 LOGI("getVideoHeight");
 *h = 430;
 return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::getCurrentPosition(int *msec){
 LOGI("getCurrentPosition");
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::getDuration(int *msec){
 LOGI("getDuration");
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::seekTo(int msec){
 LOGI("seekTo");
    return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::reset(){
 LOGI("reset");  
 return NO_ERROR;
}

// ----------------------------------------------------------------------------------------------------------------
status_t myPlayer::setLooping(int loop){
  LOGI("setLooping");
 return NO_ERROR;
}

}; // namespace android


This class can be used as template for new player development. I created a project under ./external folder and linked this as libmyplayer to libmediaservice library. I used the ApiDemos/medial player to test this myPlayer engine



4 comments:

Jay Chawda said...

Thanks a lot... the information is really useful. I am new to Android NDK, can you please mail me the source code / project folder.My email is "jay.chawda at gmail.com". Thanks

venk said...

wow... kudos

john brito said...

Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.

rpa training in bangalore
best rpa training in bangalore
RPA training in bangalore
RPA courses in bangalore

Suresh said...
This comment has been removed by the author.