Saturday, June 20, 2009

The flow of events during the Media Player Creation in Android Framework

The flow of events of the Android media player is complex. Moreover as i don't have a Linux machine, i have to depend on the careful study of the framework source code. i think i have got a strong enough reason to buy an Ubuntu machine.

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 an opaque reference to it 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);
........
........

........
........
status_t opStatus = mp->setDataSource(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)
{
......
......

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.

sp MediaPlayerService::create(pid_t pid, const sp(IMediaPlayerClient)& client, const char* url)
{
.....

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

.....

.....

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 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)
{
.....

.....

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);

.....

.....

// create the right type of player
sp(MediaPlayerBase) p = createPlayer(playerType);
......

......
mStatus = p->setDataSource(url);

if (mStatus == NO_ERROR) mPlayer = p;

return mStatus;
}
}

From the above code snippet it becomes clear that we do

either:

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;


or:

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 function looks like the following:

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

.......

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


// create the right type of player
sp(MediaPlayerBase) p = createPlayer(playerType);
........

.........

.........
}

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. And we get the playertype from the file descriptor.


In the second case (the else part) we call sp p = createPlayer(playerType). 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)
{
......

......

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)

{
.....

switch (playerType) {
#ifndef NO_OPENCORE
case PV_PLAYER:

p = new PVPlayer();

break;
#endif
case SONIVOX_PLAYER:

p = new MidiFile();

break;

case VORBIS_PLAYER:

p = new VorbisPlayer();

break;
}
......

......

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 that we pass in the client side java interface of the Mediaplyer.

6 comments:

Sagar said...

You have done excellent work man..
Thanks. It wud be nice if you can put some sequence diagram also.

Deva.R said...

Hi,

By any chance you are aware of a native media player and not a java app?

my requirement is - want to play video bypassing activity manager..

thanks,
Deva

Gordon said...

You don't need a linux machine. Just download Oracle Virtual Box (VBox). It is a very good virtual machine, and runs UBuntu and other OS's very well.

Nice work

Som said...

Thank you Gordon for sharing the information about Oracle Virtual Box... I will share this info with the student community...

ayushjain said...

i am trying to call MediaPlayer::setDataSource() function directly ... means i m not calling it through java (android) application so no JNI layer is involved ...

I have created this succesfully i am getting session id also ,,, but i am not able to call prepare() function properly in same way m getting some errors (related to memory issue) pls help ....

or pls explain prepare() fun flow as u explained for set data source ...pls

Debayan Banerjee said...

Superb info , very detail explanations .. very helpfull to me .. made many things clear ..

 
Bookmark and Share