---===NuPlayer===---
In part 4, HTTPLiveSource start downloading the m3u8 file and the successive media segments
Back to NuPlayer
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
void NuPlayer::onMessageReceived(const sp &msg) {
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();//HTTPLiveSource
mRenderer = new Renderer(
mAudioSink,
new AMessage(kWhatRendererNotify, id()));
looper()->registerHandler(mRenderer);
postScanSources();
break;
}
}
}
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerRenderer.cpp
NuPlayer::Renderer::Renderer(
const sp
const sp
: mAudioSink(sink),
mNotify(notify),
mNumFramesWritten(0),
mDrainAudioQueuePending(false),
mDrainVideoQueuePending(false),
mAudioQueueGeneration(0),
mVideoQueueGeneration(0),
mAnchorTimeMediaUs(-1),
mAnchorTimeRealUs(-1),
mFlushingAudio(false),
mFlushingVideo(false),
mHasAudio(false),
mHasVideo(false),
mSyncQueues(false),
mPaused(false),
mLastPositionUpdateUs(-1ll),
mVideoLateByUs(0ll) {
}
Note that NuPlayer pass a message "kWhatRendererNotify" to construct the NuPlayerRenderer, NuPlayerRenderer can use this message(stored as mNotify) to notify NuPlayer some information in the future
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
void NuPlayer::postScanSources() {
if (mScanSourcesPending) {
return;
}
sp
msg->setInt32("generation", mScanSourcesGeneration);
msg->post();
mScanSourcesPending = true;
}
"mScanSourcesPending=true" means the kWhatScanSources message is pending in the Looper's message queue.
void NuPlayer::onMessageReceived(const sp
switch (msg->what()) {
case kWhatScanSources:
{
mScanSourcesPending = false;
instantiateDecoder(false, &mVideoDecoder);
if (mAudioSink != NULL) {
instantiateDecoder(true, &mAudioDecoder);
}
status_t err;
if ((err = mSource->feedMoreTSData()) != OK) {
if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
// We're not currently decoding anything (no audio or
// video tracks found) and we just ran out of input data.
if (err == ERROR_END_OF_STREAM) {
notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
} else {
notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
}
}
break;
}
if (mAudioDecoder == NULL || mVideoDecoder == NULL) {
msg->post(100000ll);
mScanSourcesPending = true;
}
break;
}
}
}
status_t NuPlayer::instantiateDecoder(bool audio, sp
if (*decoder != NULL) {
return OK;
}
sp
if (meta == NULL) {
return -EWOULDBLOCK;
}
...
...
...
}
Note that mSource here refer to HTTPLiveSource.
At first, the meta == NULL and decoder == NULL because the media segment has not been downloaded or been parsed. (none of the buffer stored in mDataSource).
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\HTTPLiveSource.cpp
status_t NuPlayer::HTTPLiveSource::feedMoreTSData() {
sp
static_cast
for (int32_t i = 0; i < 50; ++i) {
char buffer[188];
ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer));
if (n == -EWOULDBLOCK) {
break;
}
...
...
}
return OK;
}
Because of the same reason above, (n == -EWOULDBLOCK) is true and break the for loop.
Therefore NuPlayer will continuously do msg->post(100000ll) to post kWhatScanSources message.
When the meta in instantiateDecoder not equal to NULL ?
Back to see what the LiveSession do when onDownloadNext() executed
In Media Framework :android_src\framework\base\media\libstagefright\httplive\LiveSession.cpp
void LiveSession::onDownloadNext() {
...
...
...
mDataSource->queueBuffer(buffer);
postMonitorQueue();
}
The buffers will be stored in mDataSource's buffer queue
Let's see feedMoreTSData() in HTTPLiveSource again
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\HTTPLiveSource.cpp
status_t NuPlayer::HTTPLiveSource::feedMoreTSData() {
sp
static_cast
for (int32_t i = 0; i < 50; ++i) {
char buffer[188];
ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer));
if (n == -EWOULDBLOCK) {
break;
}
status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
mOffset += n;
}
return OK;
}
If there are buffer in the LiveDataSource's buffer queue, then mTSParser->feedTSPacket() will be called
In Media Framework :android_src\framework\base\media\libstagefright\mpegts\ATSParser.cpp
status_t ATSParser::feedTSPacket(const void *data, size_t size) {
CHECK_EQ(size, kTSPacketSize);
ABitReader br((const uint8_t *)data, kTSPacketSize);
return parseTS(&br);
}
status_t ATSParser::parseTS(ABitReader *br) {
unsigned sync_byte = br->getBits(8);
CHECK_EQ(sync_byte, 0x47u);
...
...
return OK;
}
The parseTS() involves the format and specification of TS(transport stream),
and the process of parseTS() will end with parsePES()
status_t ATSParser::Stream::parsePES(ABitReader *br) {
...
...
onPayloadData(
PTS_DTS_flags, PTS, DTS, br->data(), dataLength);
}
void ATSParser::Stream::onPayloadData(
unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS,
const uint8_t *data, size_t size) {
LOGV("onPayloadData mStreamType=0x%02x", mStreamType);
int64_t timeUs = 0ll; // no presentation timestamp available.
if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) {
timeUs = mProgram->convertPTSToTimestamp(PTS);
}
status_t err = mQueue->appendData(data, size, timeUs);
sp
while ((accessUnit = mQueue->dequeueAccessUnit()) != NULL) {
if (mSource == NULL) {
sp
if (meta != NULL) {
LOGV("Stream PID 0x%08x of type 0x%02x now has data.",
mElementaryPID, mStreamType);
mSource = new AnotherPacketSource(meta);
mSource->queueAccessUnit(accessUnit);
}
} else if (mQueue->getFormat() != NULL) {
// After a discontinuity we invalidate the queue's format
// and won't enqueue any access units to the source until
// the queue has reestablished the new format.
if (mSource->getFormat() == NULL) {
mSource->setFormat(mQueue->getFormat());
}
mSource->queueAccessUnit(accessUnit);
}
}
}
(Here mQueue refer to ESQueue in android_src\framework\base\media\libstagefright\mpegts\ESQueue.cpp)
The onPayloadData finally put the H264 AccessUnit into mSource(refer to AnotherPacketSource)
and set the format of mSource
Back to NuPlayer
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayer.cpp
status_t NuPlayer::instantiateDecoder(bool audio, sp
if (*decoder != NULL) {
return OK;
}
sp
if (meta == NULL) {
return -EWOULDBLOCK;
}
sp
new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify,
id());
*decoder = audio ? new Decoder(notify) :
new Decoder(notify, mNativeWindow);
looper()->registerHandler(*decoder);
(*decoder)->configure(meta);
return OK;
}
Take a look of mSource->getFormat()
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\HTTPLiveSource.cpp
sp NuPlayer::HTTPLiveSource::getFormat(bool audio) {
ATSParser::SourceType type =
audio ? ATSParser::AUDIO : ATSParser::VIDEO;
sp source =
static_cast(mTSParser->getSource(type).get());
if (source == NULL) {
return NULL;
}
return source->getFormat();
}
We can get the none-null format if the process of parsePES is finished
It's time to new Decoder, note that NuPlayer also pass a message kWhatVideoNotify (if video) to construct the Decoder.
In Media Framework :android_src\framework\base\media\libmediaplayerservice\nuplayer\NuPlayerDecoder.cpp
NuPlayer::Decoder::Decoder(
const sp ¬ify,
const sp &nativeWindow)
: mNotify(notify),
mNativeWindow(nativeWindow) {
}
void NuPlayer::Decoder::configure(const sp &meta) {
sp notifyMsg =
new AMessage(kWhatCodecNotify, id());
bool needDedicatedLooper = !strncasecmp(mime, "video/", 6);
mCodec = new ACodec;
if (needDedicatedLooper && mCodecLooper == NULL) {
mCodecLooper = new ALooper;
mCodecLooper->setName("NuPlayerDecoder");
mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
}
(needDedicatedLooper ? mCodecLooper : looper())->registerHandler(mCodec);
mCodec->setNotificationMessage(notifyMsg);
mCodec->initiateSetup(format);
}
A message "kWhatCodecNotify" is passed to ACodec for notify back. The role of ACodec is like OMXCodec in Stagefright
In Media Framework :android_src\framework\base\media\libstagefright\ACodec.cpp
ACodec::ACodec()
: mNode(NULL),
mSentFormat(false) {
mUninitializedState = new UninitializedState(this);
mLoadedToIdleState = new LoadedToIdleState(this);
mIdleToExecutingState = new IdleToExecutingState(this);
mExecutingState = new ExecutingState(this);
mOutputPortSettingsChangedState =new OutputPortSettingsChangedState(this);
mExecutingToIdleState = new ExecutingToIdleState(this);
mIdleToLoadedState = new IdleToLoadedState(this);
mFlushingState = new FlushingState(this);
mPortEOS[kPortIndexInput] = mPortEOS[kPortIndexOutput] = false;
mInputEOSResult = OK;
changeState(mUninitializedState);
}
ACodec inherit AHierarchicalStateMachine, and implement the state machine.
We can see the code above with the states:
==================================
UninitializedState
LoadedToIdleState
IdleToExecutingState
ExecutingState
OutputPortSettingsChangedState
ExecutingToIdleState
IdleToLoadedState
FlushingState
==================================
In Media Framework :android_src\framework\base\media\libstagefright\foundation\AHierarchicalStateMachine.cpp
void AHierarchicalStateMachine::changeState(const sp &state) {
...
...
for (size_t i = 0; i < A.size(); ++i) {
A.editItemAt(i)->stateExited();
}
for (size_t i = B.size(); i-- > 0;) {
B.editItemAt(i)->stateEntered();
}
}
Each state except UninitializedState in ACodec override the stateEntered, but stateExited() not.
In Media Framework :android_src\framework\base\media\libstagefright\ACodec.cpp
void ACodec::setNotificationMessage(const sp &msg) {
mNotify = msg;
}
void ACodec::initiateSetup(const sp &msg) {
msg->setWhat(kWhatSetup);
msg->setTarget(id());
msg->post();
}
To be continued.
沒有留言:
張貼留言