[Android] The Story of MediaPlayer.setDataSource [Part.1]

Android MediaPlayer is a big class for all purpose media type playing, including local file and streaming playing.

The story start from MediaPlayer.setDataSource.

android.media package

android.media package locates in frameworks/base/media, and it's Java source is java/android/media.

This package provide various of interface for media playing/recording/decoding etc, including the MediaPlayer we are going to talk about today.

MediaPlayer.setDataSource()

I would not talk much about MediaPlayer constrution, the part we are insterested is what happens after calling setDataSource()

From developer.android, MediaPlayer provide 4 types of method reloading for setDataSource:

1
2
3
4
5
void setDataSource(String path)
void setDataSource(Context context, Uri uri, Map<String, String> headers)
void setDataSource(Context context, Uri uri)
void setDataSource(FileDescriptor fd, long offset, long length)
void setDataSource(FileDescriptor fd)

Although setDataSource accept various of source, including android content URI, asset fd, local file path and http/rtsp URL, it can be categoried into two type of source:

  • web source: http/https/rtsp URL
    • native API: private native void nativeSetDataSource(...)
  • file source: local file open as fd (Android content URI, asset fd, local file path)
    • native API: private native void _setDataSource(...)

Dig in

Let's take the most general API void setDataSource(String path) for example:

This API will eventually calling the below code snap, note that line 5 is the key point to branch.

  • an local file path will has a prefix file:/// in URI
  • whereas a streaming http/https/rtsp not.

we can see line 9 that if source is a remote source, it will invoke nativeSetDataSource.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    private void setDataSource(String path, String[] keys, String[] values)
            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
        final Uri uri = Uri.parse(path);
        final String scheme = uri.getScheme();
        if ("file".equals(scheme)) {
            path = uri.getPath();
        } else if (scheme != null) {
            // handle non-file sources
            nativeSetDataSource(
                MediaHTTPService.createHttpServiceBinderIfNecessary(path),
                path,
                keys,
                values);
            return;
        }

        final File file = new File(path);
        if (file.exists()) {
            FileInputStream is = new FileInputStream(file);
            FileDescriptor fd = is.getFD();
            setDataSource(fd);
            is.close();
        } else {
            throw new IOException("setDataSource failed.");
        }
    }

line 21 is the route for local file path, which will call native method '_setDataSource` in the end.

1
2
3
4
5
6
7
8
9
10
    public void setDataSource(FileDescriptor fd)
            throws IOException, IllegalArgumentException, IllegalStateException {
        // intentionally less than LONG_MAX
        setDataSource(fd, 0, 0x7ffffffffffffffL);
    }

    public void setDataSource(FileDescriptor fd, long offset, long length)
            throws IOException, IllegalArgumentException, IllegalStateException {
        _setDataSource(fd, offset, length);
    }

JNI library

Obviously, Java code can't cover all the things to play media, actually, it heavily depends on C/C++ library.

Here JNI act as a bridge for Android Java code to access C/C++ library. The corresponding JNI path for MediaPlayer is jni/android_media_MediaPlayer.cpp under frameworks/base/media.

Let's see the what was going on in the two native API methoded above.

native API declare and mapping

  • nativeSetDataSource ==> android_media_MediaPlayer_setDataSourceAndHeaders
  • _setDataSource ==> android_media_MediaPlayer_setDataSourceFD
1
2
3
4
5
6
7
8
9
static JNINativeMethod gMethods[] = {
    {
        "nativeSetDataSource",
        "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
        "[Ljava/lang/String;)V",
        (void *)android_media_MediaPlayer_setDataSourceAndHeaders
    },

    {"_setDataSource",       "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},

JNI is just a bridge

What have been done in JNI?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
        JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
        jobjectArray keys, jobjectArray values) {

    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    ...
    sp<IMediaHTTPService> httpService;
    if (httpServiceBinderObj != NULL) {
        sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
        httpService = interface_cast<IMediaHTTPService>(binder);
    }

    status_t opStatus =
        mp->setDataSource(
                httpService,
                pathStr,
                headersVector.size() > 0? &headersVector : NULL);
    ...
}

There is nothing much done in JNI code, most of work is type convert, and adopt libmedia.

libmedia

path: frameworks/av/media/libmedia/

Now we eventually come to C++ world.

what's going on in setDataSource again

file: mediaplayer.cpp

Let's take http source for example:

Note line 8, it will try to get MediaPlayerService, but wait, WTF, it still contine to call another service, endless call chain!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
status_t MediaPlayer::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url, const KeyedVector<String8, String8> *headers)
{
                    ALOGV("setDataSource(%s)", url);
        status_t err = BAD_VALUE;
        if (url != NULL) {
            const sp<IMediaPlayerService>& service(getMediaPlayerService());
        if (service != 0) {
            sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
            if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
                (NO_ERROR != player->setDataSource(httpService, url, headers))) {
                player.clear();
            }
            err = attachNewPlayer(player);
        }
    }
    return err;
}

mediaserver

path: frameworks/av/media/mediaserver

In previous section, we say that something called MediaPlayerService is invoked. This is done by this daemon.

The task done here is quite simple, by including libraries, it acts as a center to provide services for all kinds of media functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc __unused, char** argv)
{
    ...
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        MediaPlayerService::instantiate();
        CameraService::instantiate();
        AudioPolicyService::instantiate();
        SoundTriggerHwService::instantiate();
        registerExtensions();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    ...

Note in line 8, mediaserver register a service called MediaPlayerService. Yep! That's all we want to find.

MediaPlayerService

path: frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp

Yes, the story is not reach the end. Now we come to another library called libmediaplayerservice. It has an interface called MediaPlayerService.

Note that in frameworks/av/media/libmedia/mediaplayer.cpp setDataSource has the following calls. Let's have a look at it.

1
2
3
4
5
6
            /* get MediaPlayerService */
            const sp<IMediaPlayerService>& service(getMediaPlayerService());
            /* create a player by service->create */
            sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
            /* again call setDataSource */
             (NO_ERROR != player->setDataSource(httpService, url, headers))) {
service->create

Nothing special here, it create a class Client, and return it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client,
        int audioSessionId)
{
    ...
    sp<Client> c = new Client(
            this, pid, connId, client, audioSessionId,
            IPCThreadState::self()->getCallingUid());
    ...
    wp<Client> w = c;
    {
        Mutex::Autolock lock(mLock);
        mClients.add(w);
    }
    return c;
}
player->setDataSource

From previous section, we can see that player is a instance of Client. Let's have a glance at setDataSource

Since we want to investigate remote source like HTTP, we quickly jump to line 17.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
status_t MediaPlayerService::Client::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url,
        const KeyedVector<String8, String8> *headers)
{
    ...
    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);
        ...
        setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus
        ...
    } else {
        player_type playerType = MediaPlayerFactory::getPlayerType(this, url);
        sp<MediaPlayerBase> p = setDataSource_pre(playerType);
        if (p == NULL) {
            return NO_INIT;
        }

        setDataSource_post(p, p->setDataSource(httpService, url, headers));
        return mStatus;
    }
}

Here is a interesting part MediaPlayerFactory::getPlayerType(this, url);

MediaPlayerFactory

file: MediaPlayerFactory.cpp

This is a "Factory" to provide various kind of players.

1
2
3
4
5
6
7
8
void MediaPlayerFactory::registerBuiltinFactories() {
    ....
    registerFactory_l(new StagefrightPlayerFactory(), STAGEFRIGHT_PLAYER);
    registerFactory_l(new NuPlayerFactory(), NU_PLAYER);
    registerFactory_l(new SonivoxPlayerFactory(), SONIVOX_PLAYER);
    registerFactory_l(new TestPlayerFactory(), TEST_PLAYER);
    ...
}

we can infer that it provide 4 kinds of players:

  • StagefrightPlayer:
    • source: frameworks/av/media/libstagefright
    • a generic player for local file playing.
  • NuPlayer
    • source: frameworks/av/media/libmediaplayerservice/nuplayer
    • It's a built-in player, i.e. static link into libmediaplayerservice
  • SonivoxPlayer
    • source: external/sonivox/
    • For MIDI playback.
  • TestPlayer: just ignore it.
How the factory detemines which player to use?

In setDataSource we call MediaPlayerFactory::getPlayerType(this, url);, so what happens here?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              const char* url) {
    GET_PLAYER_TYPE_IMPL(client, url);
}

#define GET_PLAYER_TYPE_IMPL(a...)                      \
    Mutex::Autolock lock_(&sLock);                      \
                                                        \
    player_type ret = STAGEFRIGHT_PLAYER;               \
    float bestScore = 0.0;                              \
                                                        \
    for (size_t i = 0; i < sFactoryMap.size(); ++i) {   \
                                                        \
        IFactory* v = sFactoryMap.valueAt(i);           \
        float thisScore;                                \
        CHECK(v != NULL);                               \
        thisScore = v->scoreFactory(a, bestScore);      \
        if (thisScore > bestScore) {                    \
            ret = sFactoryMap.keyAt(i);                 \
            bestScore = thisScore;                      \
        }                                               \
    }                                                   \
                                                        \
    if (0.0 == bestScore) {                             \
        ret = getDefaultPlayerType();                   \
    }                                                   \
                                                        \
    return ret;

Emm, an annoying Marco, but line 17 has an interesting method call scoreFactory.

This is a scoring system, it will return the player which score highest among all available media players.

scoreFactory in NuPlayer

But what's happens in scoreFactory, take a look at NuPlayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class NuPlayerFactory : public MediaPlayerFactory::IFactory {
  public:
    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const char* url,
                               float curScore) {
        static const float kOurScore = 0.8;

        if (kOurScore <= curScore)
            return 0.0;

        if (!strncasecmp("http://", url, 7)
                || !strncasecmp("https://", url, 8)
                || !strncasecmp("file://", url, 7)) {
            size_t len = strlen(url);
            if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) {
                return kOurScore;
            }

            if (strstr(url,"m3u8")) {
                return kOurScore;
            }

            if ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?")) {
                return kOurScore;
            }
        }

        if (!strncasecmp("rtsp://", url, 7)) {
            return kOurScore;
        }

        return 0.0;
    }

from line 11~30, it tells us that NuPlayer is good at playing streaming media (".m3u8", i.e. http/https live streaming, "rtsp" and "sdp").

So if we passing a streaming media URL to MediaPlayer, in most case, NuPlayer will server us.

留言