Continued (4)
Start NuPlayer tracing
---===NuPlayer===---
Recall in Android 4.0 Ice Cream Sandwich Media Framework (1), if the data source URL contains m3u8 (ex: http://192.168.1.3/playlist.m3u8), or start with rtsp protocol, then NU_PLAYER will be selected by MediaPlayerService.
Here, the NuPlayer tracing will focus on HTTP Live Streaming, which means the m3u8 file decode.
Before start the code tracing, we first take a look about the m3u8 file.
(the URL to set for MediaPlayer in APK is like:http://192.168.1.3/playlist.m3u8 )
==playlist.m3u8 =================================
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID:1,bandwidth=600000
http://192.168.1.22/playhigh.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID:1,bandwidth=200000
http://192.168.1.21/playlow.m3u8
==playhigh.m3u8=================================
#EXTM3U
#EXT-X-TARGETDURATION:5
#EXTINF:5,
http://192.168.1.22/high/sample-no-1.ts
#EXTINF:5,
http://192.168.1.22/high/sample-no-2.ts
#EXTINF:5,
http://192.168.1.22/high/sample-no-3.ts
#EXT-X-ENDLIST
==playlow.m3u8=================================
#EXTM3U
#EXT-X-TARGETDURATION:5
#EXTINF:5,
http://192.168.1.21/low/sample-no-1.ts
#EXTINF:5,
http://192.168.1.21/low/sample-no-2.ts
#EXTINF:5,
http://192.168.1.21/low/sample-no-3.ts
#EXT-X-ENDLIST
=============================================
All the tags like "#EXTM3U" and others are defined in Apple's HTTP Live Streaming protocol
see the following website for more information
http://tools.ietf.org/html/draft-pantos-http-live-streaming-07
The introduction of HTTP Live Streaming
see the following website for more information
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008332-CH1-DontLinkElementID_39
And, the supported version of HTTP Live Streaming in Android :
http://developer.android.com/guide/appendix/media-formats.html
==================================================================
Let's get it started.
In APK, we use the same process to start media playback
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(path);
mMediaPlayer.prepare();
mMediaPlayer.start();
In Media Framework :android_src\framework\base\media\libmediaplayerservice\MediaPlayerService.cpp
static sp
notify_callback_f notifyFunc)
{
sp
switch (playerType) {
case SONIVOX_PLAYER:
LOGD(" create MidiFile");
p = new MidiFile();
break;
case STAGEFRIGHT_PLAYER:
LOGD(" create StagefrightPlayer");
p = new StagefrightPlayer;
break;
case NU_PLAYER:
LOGD(" create NuPlayer");
p = new NuPlayerDriver;
break;
case TEST_PLAYER:
LOGD("Create Test Player stub");
p = new TestPlayerStub();
break;
default:
LOGD("Unknown player type: %d", playerType);
return NULL;
}
}
Just mentioned before, if the URL contains m3u8, the NU_PLAYER will be selected. Therefore, here NuPlayerDriver will be newed.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerDriver.cpp
NuPlayerDriver::NuPlayerDriver()
: mDurationUs(-1),
mPositionUs(-1),
mNumFramesTotal(0),
mNumFramesDropped(0),
mState(UNINITIALIZED),
mLooper(new ALooper),{
mLooper->setName("NuPlayerDriver Looper");
mLooper->start();
mPlayer = new NuPlayer;
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);
}
NuPlayerDriver's constructor initialize the Looper/Message/Handler mechanism for NuPlayer.
The role of NuPlayerDriver is like StagefrightPlayer, the interface with MediaPlayer.
mPlayer->setDriver(this) let NuPlayer has the ability to send messages back.(media durations, number of frames, etc).
Note here mState is set as UNINITIALIZED.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
NuPlayer::NuPlayer()
: mVideoIsAVC(false),
mAudioEOS(false),
mVideoEOS(false),
mVideoLateByUs(0ll),
mNumFramesTotal(0ll),
mNumFramesDropped(0ll) {
}
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerDriver.cpp
status_t NuPlayerDriver::setDataSource(
const char *url, const KeyedVector
CHECK_EQ((int)mState, (int)UNINITIALIZED);
mPlayer->setDataSource(url, headers);
mState = STOPPED;
}
Note here mState is set as STOPPED .
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
void NuPlayer::setDataSource(
const char *url, const KeyedVector
sp
if (!strncasecmp(url, "rtsp://", 7)) {
msg->setObject(
"source", new RTSPSource(url, headers, mUIDValid, mUID));
} else {
msg->setObject(
"source", new HTTPLiveSource(url, headers, mUIDValid, mUID));
}
msg->post();
}
If url start with "rtsp://", then RTSPSource will be instantiated, else (for m3u8)HTTPLiveSource will be instantiated.
Both of them are used to handle the incoming media stream.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\HTTPLiveSource.cpp
NuPlayer::HTTPLiveSource::HTTPLiveSource(
const char *url,
const KeyedVector
bool uidValid, uid_t uid)
: mURL(url),
mFlags(0),
mFinalResult(OK),
mOffset(0) {
}
Then a message kWhatSetDataSource posted. Note that the message contains the instance of RTSPSource or HTTPLiveSource.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
void NuPlayer::onMessageReceived(const sp
switch (msg->what()) {
case kWhatSetDataSource:
{
LOGV("kWhatSetDataSource");
CHECK(mSource == NULL);
sp
CHECK(msg->findObject("source", &obj));
mSource = static_cast(obj.get());
break;
}
}
}
Here, mSource refer to HTTPLiveSource or RTSPSource
Now we finish the mMediaPlayer.setDataSource(path);
In Media Framework :android_src\framework\base\media\libmediaplayerservice\MediaPlayerService.cpp
status_t MediaPlayerService::Client::prepareAsync()
{
LOGV("[%d] prepareAsync", mConnId);
sp
if (p == 0) return UNKNOWN_ERROR;
status_t ret = p->prepareAsync();
return ret;
}
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerDriver.cpp
status_t NuPlayerDriver::prepareAsync() {
notifyListener(MEDIA_PREPARED);
return OK;
}
The prepareAsync() here do nothing but notify back MEDIA_PREPARED
In Media Framework :android_src\framework\base\media\libmediaplayerservice\MediaPlayerService.cpp
status_t MediaPlayerService::Client::start()
{
LOGV("[%d] start", mConnId);
sp
return p->start();
}
Note that mState of NuPlayerDriver is set as STOPPED now.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerDriver.cpp
status_t NuPlayerDriver::start() {
switch (mState) {
case STOPPED:
{
mAtEOS = false;
mPlayer->start();
break;
}
mState = PLAYING;
return OK;
}
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
void NuPlayer::start() {
(new AMessage(kWhatStart, id()))->post();
}
NuPlayer use message to post kWhatStart.
void NuPlayer::onMessageReceived(const sp
switch (msg->what()) {
case kWhatStart:
{
LOGV("kWhatStart");
mVideoIsAVC = false;
mAudioEOS = false;
mVideoEOS = false;
mSkipRenderingAudioUntilMediaTimeUs = -1;
mSkipRenderingVideoUntilMediaTimeUs = -1;
mVideoLateByUs = 0;
mNumFramesTotal = 0;
mNumFramesDropped = 0;
mSource->start();
mRenderer = new Renderer(
mAudioSink,
new AMessage(kWhatRendererNotify, id()));
looper()->registerHandler(mRenderer);
postScanSources();
break;
}
}
}
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\HTTPLiveSource.cpp
void NuPlayer::HTTPLiveSource::start() {
mLiveLooper = new ALooper;
mLiveLooper->setName("http live");
mLiveLooper->start();
mLiveSession = new LiveSession(
(mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0,
mUIDValid, mUID);
mLiveLooper->registerHandler(mLiveSession);
mLiveSession->connect(
mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
mTSParser = new ATSParser;
}
HTTPLiveSource create it own Looper for Message/Handler mechanism, and instantiate the LiveSession
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveSession.cpp
LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid)
: mDataSource(new LiveDataSource),
mHTTPDataSource(
HTTPBase::Create(
(mFlags & kFlagIncognito)
? HTTPBase::kFlagIncognito
: 0)),
mPrevBandwidthIndex(-1),
mLastPlaylistFetchTimeUs(-1),
mSeqNumber(-1),
mSeekTimeUs(-1),
mNumRetries(0),
mDurationUs(-1),
mSeekDone(false),
mDisconnectPending(false),
mMonitorQueueGeneration(0),
mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) {
}
mDataSource is an instance of LiveDataSource, which acts as the buffer queue manager. we'll see how it work later.
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveDataSource.cpp
LiveDataSource::LiveDataSource()
: mOffset(0),
mFinalResult(OK),
mBackupFile(NULL) {
}
In Media Framework :android_src\framework\base\media\libstagefright\HTTPBase.cpp
sp
return new ChromiumHTTPDataSource(flags);
}
So, the mHTTPDataSource in LiveSession is an instance of ChromiumHTTPDataSource.
ChromiumHTTPDataSource will help LiveSession to read data from internet.
In Media Framework :android_src\framework\base\media\libstagefright\chromium_http\ChromiumHTTPDataSource.cpp
ChromiumHTTPDataSource::ChromiumHTTPDataSource(uint32_t flags)
: mFlags(flags),
mState(DISCONNECTED),
mDelegate(new SfDelegate),
mCurrentOffset(0),
mIOResult(OK),
mContentSize(-1),
mDecryptHandle(NULL),
mDrmManagerClient(NULL) {
mDelegate->setOwner(this);
LOG_PRI(ANDROID_LOG_VERBOSE, LOG_TAG,"ChromiumHTTPDataSource ctor");
}
Now back to NuPlayer::HTTPLiveSource::start() and do mLiveSession->connect()
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveSession.cpp
void LiveSession::connect(
const char *url, const KeyedVector
sp
msg->setString("url", url);
if (headers != NULL) {
msg->setPointer(
"headers",
new KeyedVector
}
msg->post();
}
We pause here and take a look of new ATSParser, used for future parsing.
In Media Framework :android_src\framework\base\media\libstagefright\mpegts\ATSParser.cpp
ATSParser::ATSParser(uint32_t flags)
: mFlags(flags) {
}
ATSParser 's constructor doesn't do anything special, so we continue to see how to handle the message kWhatConnect posted in LiveSession::connect()
void LiveSession::onMessageReceived(const sp
switch (msg->what()) {
case kWhatConnect:
onConnect(msg);
break;
}
}
void LiveSession::onConnect(const sp
CHECK(msg->findString("url", &url));
bool dummy;
sp
if (playlist->isVariantPlaylist()) {
for (size_t i = 0; i < playlist->size(); ++i) {
BandwidthItem item;
sp
playlist->itemAt(i, &item.mURI, &meta);
unsigned long bandwidth;
CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
LOGD("Check Bandwidth : %lu",item.mBandwidth);
mBandwidthItems.push(item);
}
CHECK_GT(mBandwidthItems.size(), 0u);
mBandwidthItems.sort(SortByBandwidth);
}else{
LOGD("Not VariantPlayList");
}
postMonitorQueue();
}
sp
*unchanged = false;
status_t err = fetchFile(url, &buffer);
sp
new M3UParser(url, buffer->data(), buffer->size());
return playlist;
}
fetchPlaylist here will read the data of m3u8 file from internet, and instantiate M3UParser for parsing the content of m3u8 file
playlist->isVariantPlaylist() judge if the playlist contains the tag "#EXT-X-STREAM-INF"
If contains, the playlist has the bandwidth information in the playlist.
In Media Framework :android_src\framework\base\media\libstagefright\httplive\M3UParser.cpp
M3UParser::M3UParser(
const char *baseURI, const void *data, size_t size)
: mInitCheck(NO_INIT),
mBaseURI(baseURI),
mIsExtM3U(false),
mIsVariantPlaylist(false),
mIsComplete(false) {
mInitCheck = parse(data, size);
}
The parse() in M3UParser analyzes the tags in m3u8 file.
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveSession.cpp
void LiveSession::postMonitorQueue(int64_t delayUs) {
sp
msg->setInt32("generation", ++mMonitorQueueGeneration);
msg->post(delayUs);
}
void LiveSession::onMessageReceived(const sp
switch (msg->what()) {
case kWhatMonitorQueue:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
if (generation != mMonitorQueueGeneration) {
// Stale event
break;
}
onMonitorQueue();
break;
}
}
}
void LiveSession::onMonitorQueue() {
if (mSeekTimeUs >= 0
|| mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) {
onDownloadNext();
} else {
postMonitorQueue(1000000ll);
}
}
mDataSource at first with no buffer from internet ,so here triggering onDownloadNext() to download
In Media Framework :android_src\framework\base\media\libstagefright\include\LiveSession.h
enum {
kMaxNumQueuedFragments = 3,
kMaxNumRetries = 5,
};
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveSession.cpp
void LiveSession::onDownloadNext() {
size_t bandwidthIndex = getBandwidthIndex();
if (mLastPlaylistFetchTimeUs < 0
|| (ssize_t)bandwidthIndex != mPrevBandwidthIndex
|| (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
AString url;
if (mBandwidthItems.size() > 0) {
url = mBandwidthItems.editItemAt(bandwidthIndex).mURI;
} else {
url = mMasterURL;
}
bool firstTime = (mPlaylist == NULL);
bool unchanged;
sp
if (playlist == NULL) {
if (unchanged) {
// We succeeded in fetching the playlist, but it was
// unchanged from the last time we tried.
} else {
LOGE("failed to load playlist at url '%s'", url.c_str());
mDataSource->queueEOS(ERROR_IO);
return;
}
} else {
mPlaylist = playlist;
}
if (firstTime) {
Mutex::Autolock autoLock(mLock);
if (!mPlaylist->isComplete()) {
mDurationUs = -1;
} else {
mDurationUs = 0;
for (size_t i = 0; i < mPlaylist->size(); ++i) {
sp
CHECK(mPlaylist->itemAt(
i, NULL /* uri */, &itemMeta));
int64_t itemDurationUs;
CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
mDurationUs += itemDurationUs;
}
}
}
sp
status_t err = fetchFile(uri.c_str(), &buffer);
mDataSource->queueBuffer(buffer);
mPrevBandwidthIndex = bandwidthIndex;
++mSeqNumber;
postMonitorQueue();
}
onDownloadNext() downloads a media segment list in playlist and queue the downloaded buffer to mDataSource, then continuously postMonitorQueue() to download next
沒有留言:
張貼留言