SOM-ITSOLUTIONS

Android

Flow of events during Media Player Creation in Android

SOMENATH MUKHOPADHYAY

som-itsolutions

#A2 1/13 South Purbachal Hospital Road Kolkata 700078 Mob: +91 9748185282

Email: som@som-itsolutions.com / som.mukhopadhyay@gmail.com

Website: http://www.som-itsolutions.com/

Blog: www.som-itsolutions.blogspot.com


The flow of events of the Android media player is complex. This document will serve as a hand-holding for code walkthrough of the Android multimedia framework for the Android lovers.

To start with, let me give you the call stack of the media player framework in Android. This has been depicted as in the following diagram.

MediaPlayer.png

Now lets come to the fact findings. There are two sides of the Android Media Framework. What we as an user see is the Java interface which is called the Mediaplayer.java. However, this java interface interacts with a native mediaplayer object through Java Native Interface (JNI) mechanism. This interaction is done through the functionalities defined in Android_media_Mediaplayer.cpp. In this file the framework engineers have kept all the necessary JNI functions.

Now when we are about to start the MediaPlayer, the JNI function that is called is the

private native final void native_setup.

This function is actually responsible for creating a C++ mediaplayer object in the native side and storing it as an opaque reference in the Java client side. So when we interact with the client side Java mediaplayer object, we internally interact with this native C++ object.

The JNI layer actually delegates its task to a Mediaplayer object. The functionalities of this C++ class are defined in the file Mediaplayer.cpp.

Now the next step is that we set a data source in the Java side using the function setDataSource function in which the URI of the data source is passed. This in turn calls the native function  

android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)

In the native source, this function is defined as

android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)

{

sp<MediaPlayer> mp = getMediaPlayer(env, thiz);

if (mp == NULL ) {

   jniThrowException(env, "java/lang/IllegalStateException", NULL);

   return;

}

if (path == NULL) {

   jniThrowException(env, "java/lang/IllegalArgumentException", NULL);

   return;

}

const char *pathStr = env->GetStringUTFChars(path, NULL);

if (pathStr == NULL) {  // Out of memory

   jniThrowException(env, "java/lang/RuntimeException", "Out of memory");

   return;

}

LOGV("setDataSource: path %s", pathStr);

status_t opStatus = mp->setDataSource(pathStr);

// Make sure that local ref is released before a potential exception

env->ReleaseStringUTFChars(path, pathStr);

process_media_player_call( env, thiz, opStatus, "java/io/IOException", "setDataSource failed." );

}

Look at the line:

status_t opStatus = mp->setDataSource(pathStr);

This function is defined as

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;

}

Look at the line :

sp<IMediaPlayer> player(service->create(getpid(), this, url));

It actually takes the help of the IMediaPlayerservice layer and calls the create function on this. This function can be found in \\base\media\libmedia\IMediaPlayerService.cpp file.

The create function of the MediaPlayerService looks like the following. It can be found at \\base\media\libmediaplayerservice\MediaPlayerService.cpp.

        

sp<IMediaPlayer>         MediaPlayerService::create(pid_t pid, const         

sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)

{

int32_t connId = android_atomic_inc(&mNextConnId);

sp<Client> c = new Client(this, pid, connId, client);

LOGV("Create new client(%d) from pid %d, url=%s, connId=%d", connId, pid, url, connId);

if (NO_ERROR != c->setDataSource(url))

{

   c.clear();

   return c;

}

wp<Client> w = c;

Mutex::Autolock lock(mLock);

mClients.add(w);

return c;

}

Look at the line :

sp<Client> c = new Client(this, pid, connId, client)

The client constructor is as the following:

sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)

{

int32_t connId = android_atomic_inc(&mNextConnId);

sp<Client> c = new Client(this, pid, connId, client);

LOGV("Create new client(%d) from pid %d, url=%s, connId=%d", connId, pid, url, connId);

if (NO_ERROR != c->setDataSource(url))

{

   c.clear();

   return c;

}

wp<Client> w = c;

Mutex::Autolock lock(mLock);

mClients.add(w);

return c;

}

Now look at the following line (line number 6) of sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)

if (NO_ERROR != c->setDataSource(url))

So we are basically calling the setDataSource on the Client.

This function is like the following:

status_t MediaPlayerService::Client::setDataSource(const char *url)

{

LOGV("setDataSource(%s)", url);

if (url == NULL)

   return UNKNOWN_ERROR;

if (strncmp(url, "content://", 10) == 0) {

   // get a filedescriptor for the content Uri and

   // pass it to the setDataSource(fd) method

   String16 url16(url);

   int fd = android::openContentProviderFile(url16);

   if (fd < 0)

   {

       LOGE("Couldn't open fd for %s", url);

       return UNKNOWN_ERROR;

   }

   setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus

   close(fd);

   return mStatus;

} else {

   player_type playerType = getPlayerType(url);

   LOGV("player type = %d", playerType);

   // create the right type of player

   sp<MediaPlayerBase> p = createPlayer(playerType);

   if (p == NULL) return NO_INIT;

   if (!p->hardwareOutput()) {

       mAudioOutput = new AudioOutput();

       static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);

   }

   // now set data source

   LOGV(" setDataSource");

   mStatus = p->setDataSource(url);

   if (mStatus == NO_ERROR) mPlayer = p;

   return mStatus;

}

}

From the above code snippet it becomes clear that either we do

if (strncmp(url, "content://", 10) == 0) {

   // get a filedescriptor for the content Uri and

   // pass it to the setDataSource(fd) method

   String16 url16(url);

   int fd = android::openContentProviderFile(url16);

   if (fd < 0)

   {

       LOGE("Couldn't open fd for %s", url);

       return UNKNOWN_ERROR;

   }

   setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus

   close(fd);

   return mStatus;

else {

   player_type playerType = getPlayerType(url); //here it extracts the Player Type from the URL.

   LOGV("player type = %d", playerType);

   // create the right type of player

   sp<MediaPlayerBase> p = createPlayer(playerType);

  ..............

  ..............

In the first case the setDataSource (line 12) function looks like the following:

status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)

{

LOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length);

struct stat sb;

int ret = fstat(fd, &sb);

if (ret != 0) {

   LOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno));

   return UNKNOWN_ERROR;

}

LOGV("st_dev  = %llu", sb.st_dev);

LOGV("st_mode = %u", sb.st_mode);

LOGV("st_uid  = %lu", sb.st_uid);

LOGV("st_gid  = %lu", sb.st_gid);

LOGV("st_size = %llu", sb.st_size);

if (offset >= sb.st_size) {

   LOGE("offset error");

   ::close(fd);

   return UNKNOWN_ERROR;

}

if (offset + length > sb.st_size) {

   length = sb.st_size - offset;

   LOGV("calculated length = %lld", length);

}

player_type playerType = getPlayerType(fd, offset, length); //here it gets the file type from the File descriptor

LOGV("player type = %d", playerType);

// create the right type of player

sp<MediaPlayerBase> p = createPlayer(playerType);

if (p == NULL) return NO_INIT;

if (!p->hardwareOutput()) {

   mAudioOutput = new AudioOutput();

   static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);

}

// now set data source

mStatus = p->setDataSource(fd, offset, length);

if (mStatus == NO_ERROR) mPlayer = p;

return mStatus;

}

Look at the line :

sp<MediaPlayerBase> p = createPlayer(playerType). It becomes clear that we create the player here.

The sp<MediaPlayerBase> p = createPlayer (playerType) actually creates the right player.

In the second case (the else part) we call sp<MediaPlayerBase> p = createPlayer(playerType) at line 6. We extract the file type from the URL. This helps us in creating the right player object.

The createPlayer function looks like the following:

sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType)

{

// determine if we have the right player type

sp<MediaPlayerBase> p = mPlayer;

if ((p != NULL) && (p->playerType() != playerType)) {

   LOGV("delete player");

   p.clear();

}

if (p == NULL) {

   p = android::createPlayer(playerType, this, notify);

}

return p;

}

Hence it actually delegates the task to p = android::createPlayer(playerType, this, notify);

The above function is as follows;

static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,

   notify_callback_f notifyFunc)

{

sp<MediaPlayerBase> p;

switch (playerType) {

#ifndef NO_OPENCORE

   case PV_PLAYER:

       LOGV(" create PVPlayer");

       p = new PVPlayer();

       break;

#endif

   case SONIVOX_PLAYER:

       LOGV(" create MidiFile");

       p = new MidiFile();

       break;

   case VORBIS_PLAYER:

       LOGV(" create VorbisPlayer");

       p = new VorbisPlayer();

       break;

}

if (p != NULL) {

   if (p->initCheck() == NO_ERROR) {

       p->setNotifyCallback(cookie, notifyFunc);

   } else {

       p.clear();

   }

}

if (p == NULL) {

   LOGE("Failed to create player object");

}

return p;

}

Thus we find that the right player is created through the parameterized factory function createPlayer.

i hope this explains how the right mediaplayer is created from the Uri passed in the Java client side interface of the Mediaplyer.