标签:method bst rand unknown create 输入 一段 取出 count
本文将基于Android N源码对NuPlayer做一个详解。NuPlayer是Android中本地和流媒体播放所用的播放器。
首先介绍NuPlayer中无处不在的AHandler机制
frameworks/av/include/media/stagefright/foundation/
frameworks/av/media/libstagefright/foundation/
AHandler是Android native层实现的一个异步消息机制,在这个机制中所有的处理都是异步的,将变量封装到一个消息AMessage结构体中,然后放到队列中去,后台专门有一个线程会从这个队列中取出消息然后执行,执行函数就是onMessageReceived。
Ahandler机制包括以下几个类
消息类,用于构造消息,通过post方法投递出去给ALooper
status_t AMessage::post(int64_t delayUs) {
sp<ALooper> looper = mLooper.promote();
if (looper == NULL) {
ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
return -ENOENT;
}
looper->post(this, delayUs);
return OK;
}
void AMessage::deliver() {
sp<AHandler> handler = mHandler.promote();
if (handler == NULL) {
ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
return;
}
handler->deliverMessage(this); //see AHandler@deliverMessage,前面通过looper post最后就是调用这里的deliever送到handler手里
}
void AHandler::deliverMessage(const sp<AMessage> &msg) {
onMessageReceived(msg);
mMessageCounter++;
….
}
// posts a message on this looper with the given timeout
void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
Mutex::Autolock autoLock(mLock);
int64_t whenUs;
if (delayUs > 0) {
whenUs = GetNowUs() + delayUs;
} else {
whenUs = GetNowUs();
}
List<Event>::iterator it = mEventQueue.begin();
while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
++it;
}
Event event;
event.mWhenUs = whenUs;
event.mMessage = msg;
if (it == mEventQueue.begin()) {
mQueueChangedCondition.signal();
}
mEventQueue.insert(it, event);
}
----------------------------------------------------------
status_t ALooper::start(
bool runOnCallingThread, bool canCallJava, int32_t priority) {
if (runOnCallingThread) {
{
Mutex::Autolock autoLock(mLock);
if (mThread != NULL || mRunningLocally) {
return INVALID_OPERATION;
}
mRunningLocally = true;
}
do {
} while (loop());
return OK;
}
Mutex::Autolock autoLock(mLock);
if (mThread != NULL || mRunningLocally) {
return INVALID_OPERATION;
}
mThread = new LooperThread(this, canCallJava);
status_t err = mThread->run(
mName.empty() ? "ALooper" : mName.c_str(), priority);
if (err != OK) {
mThread.clear();
}
return err;
}
bool ALooper::loop() {
Event event;
{
Mutex::Autolock autoLock(mLock);
if (mThread == NULL && !mRunningLocally) {
return false;
}
if (mEventQueue.empty()) {
mQueueChangedCondition.wait(mLock);
return true;
}
int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
int64_t nowUs = GetNowUs();
if (whenUs > nowUs) {
int64_t delayUs = whenUs - nowUs;
mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
return true;
}
event = *mEventQueue.begin();
mEventQueue.erase(mEventQueue.begin());
}
event.mMessage->deliver(); //see AHandler.deliverMessage
…..
return true;
}
virtual status_t readyToRun() {
mThreadId = androidGetThreadId();
return Thread::readyToRun();
}
virtual bool threadLoop() {
return mLooper->loop();
}
ALooper::handler_id ALooperRoster::registerHandler(
const sp<ALooper> looper, const sp<AHandler> &handler) {
Mutex::Autolock autoLock(mLock);
if (handler->id() != 0) {
CHECK(!"A handler must only be registered once.");
return INVALID_OPERATION;
}
HandlerInfo info;
info.mLooper = looper;
info.mHandler = handler;
ALooper::handler_id handlerID = mNextHandlerID++;//一对一
mHandlers.add(handlerID, info);//一对多
handler->setID(handlerID, looper);
return handlerID;
}
void ALooperRoster::unregisterHandler(ALooper::handler_id handlerID) {
Mutex::Autolock autoLock(mLock);
ssize_t index = mHandlers.indexOfKey(handlerID);
if (index < 0) {
return;
}
const HandlerInfo &info = mHandlers.valueAt(index);
sp<AHandler> handler = info.mHandler.promote();
if (handler != NULL) {
handler->setID(0, NULL);
}
mHandlers.removeItemsAt(index);
}
void ALooperRoster::unregisterStaleHandlers() {
Vector<sp<ALooper> > activeLoopers;
{
Mutex::Autolock autoLock(mLock);
for (size_t i = mHandlers.size(); i > 0;) {
i--;
const HandlerInfo &info = mHandlers.valueAt(i);
sp<ALooper> looper = info.mLooper.promote();
if (looper == NULL) {
ALOGV("Unregistering stale handler %d", mHandlers.keyAt(i));
mHandlers.removeItemsAt(i);
} else {
// At this point ‘looper‘ might be the only sp<> keeping
// the object alive. To prevent it from going out of scope
// and having ~ALooper call this method again recursively
// and then deadlocking because of the Autolock above, add
// it to a Vector which will go out of scope after the lock
// has been released.
activeLoopers.add(looper);
}
}
}
}
sp<ALooper> mLooper = new ALooper; //创建一个Alooper实例
sp<AHandlerReflector> mHandler = new AHandlerReflector //创建一个Ahandler实例
mLooper->setName(“xxxxx”); //设置looper名字
mLooper->start(false, true, PRIORITY_XXX); //根据参数创建并启动 looper thread
mLooper->regiserHandler(mHandler); //register handler 会调用AHandler的setID方法将looper设置到Handler里去
sp<AMessage> msg = new AMessage(kWhatSayGoodbye, mHandler); //在AMessage的构造方法里获取Ahandler对应的Looper并保存
msg->post(); // 调用looper的post方法
Message Post的调用过程
Message::post
↓
ALooper::post
mEventQueue.insert
mQueueChangedCondition.signal() //如果之前没有event,通知looper thread
↓
ALooper::loop()
if (mEventQueue.empty()) { //如果消息队列为空,则等待
mQueueChangedCondition.wait(mLock);
return true;
}
event = *mEventQueue.begin();
event.mMessage->deliver();
↓
AHandler::deliverMessage
↓
AHandlerReflector:: onMessageReceived
↓
具体的实现
下面就进入我们的正题,NuPlayer
frameworks/av/media/libmediaplayerservice/nuplayer/
NuPlayerDriver::NuPlayerDriver(pid_t pid)
: mState(STATE_IDLE), //对应播放器状态机的初始化状态
mIsAsyncPrepare(false),
mAsyncResult(UNKNOWN_ERROR),
mSetSurfaceInProgress(false),
mDurationUs(-1),
mPositionUs(-1),
mSeekInProgress(false),
mLooper(new ALooper),
mPlayerFlags(0),
mAtEOS(false),
mLooping(false),
mAutoLoop(false) {
ALOGV("NuPlayerDriver(%p)", this);
//和前面所述的异步消息创建机制相符
mLooper->setName("NuPlayerDriver Looper");
mLooper->start(
false, /* runOnCallingThread */
true, /* canCallJava */
PRIORITY_AUDIO);
//mPlayer即NuPlayer,继承于AHandler
mPlayer = AVNuFactory::get()->createNuPlayer(pid);
mLooper->registerHandler(mPlayer);
mPlayer->setDriver(this);
}
NuPlayerDriver::~NuPlayerDriver() {
ALOGV("~NuPlayerDriver(%p)", this);
mLooper->stop(); //整个NuPlayerDriver就是一个大ALooper
}
sp<NuPlayer> AVNuFactory::createNuPlayer(pid_t pid) {
return new NuPlayer(pid);
}
sp<NuPlayer::DecoderBase> AVNuFactory::createPassThruDecoder(
const sp<AMessage> ¬ify,
const sp<NuPlayer::Source> &source,
const sp<NuPlayer::Renderer> &renderer) {
return new NuPlayer::DecoderPassThrough(notify, source, renderer);
}
sp<NuPlayer::DecoderBase> AVNuFactory::createDecoder(
const sp<AMessage> ¬ify,
const sp<NuPlayer::Source> &source,
pid_t pid,
const sp<NuPlayer::Renderer> &renderer) {
return new NuPlayer::Decoder(notify, source, pid, renderer);
}
sp<NuPlayer::Renderer> AVNuFactory::createRenderer(
const sp<MediaPlayerBase::AudioSink> &sink,
const sp<AMessage> ¬ify,
uint32_t flags) {
return new NuPlayer::Renderer(sink, notify, flags);
}
下面分别分析Source, Decoder, Renderer
以setDataSource为切入点
status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) {
ALOGV("setDataSource(%p) stream source", this);
Mutex::Autolock autoLock(mLock);
if (mState != STATE_IDLE) {
return INVALID_OPERATION;
}
mState = STATE_SET_DATASOURCE_PENDING;
mPlayer->setDataSourceAsync(source);//因为driver只是NuPlayer的封装,所以还是要去调用NuPlayer完成实际动作
while (mState == STATE_SET_DATASOURCE_PENDING) {
mCondition.wait(mLock);
}
return mAsyncResult;
}
--------------------------------------
void NuPlayer::setDataSourceAsync(const sp<IStreamSource> &source) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, this);
sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);
msg->setObject("source", new StreamingSource(notify, source));
msg->post(); //到了NuPlayer中,也不是直接进行操作,而是先发个消息,验证前面所说的一切都由AMessage驱动
}
---------------------------------------
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatSetDataSource://实际的处理在这里
{
ALOGV("kWhatSetDataSource");
CHECK(mSource == NULL);
status_t err = OK;
sp<RefBase> obj;
CHECK(msg->findObject("source", &obj));
if (obj != NULL) {
Mutex::Autolock autoLock(mSourceLock);
mSource = static_cast<Source *>(obj.get());//赋值给mSource
} else {
err = UNKNOWN_ERROR;
}
CHECK(mDriver != NULL);
sp<NuPlayerDriver> driver = mDriver.promote();
if (driver != NULL) {
driver->notifySetDataSourceCompleted(err);//通知driver设置完毕
}
break;
}......
---------------------------------------
void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) {
Mutex::Autolock autoLock(mLock);
CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING);
mAsyncResult = err;
mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE;//回到driver中,流转播放器状态进入下一阶段
mCondition.broadcast();
}
下面就来看看具体有哪些source,它们都继承自NuPlayer:Source(NuPlayerSource.h & NuPlayerSource.cpp)
1.HTTP-进一步判断是以下的哪一种:HTTPLiveSource,RTSPSource,GenericSource
2.File-GenericSource
3.StreamSource-StreamingSource
4.DataSource-GenericSource
nuplayer/GenericSource.h & GenericSource.cpp
几个水位
static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
static int64_t kHighWaterMarkUs = 5000000ll; // 5secs
static int64_t kHighWaterMarkRebufferUs = 15000000ll; // 15secs,这一个是新增加的水位
static const ssize_t kLowWaterMarkBytes = 40000;
static const ssize_t kHighWaterMarkBytes = 200000;
status_t NuPlayer::GenericSource::initFromDataSource() {
init extractor;get track info and metadata
}
void NuPlayer::GenericSource::prepareAsync() {
if (mLooper == NULL) {
mLooper = new ALooper;
mLooper->setName("generic");
mLooper->start();
mLooper->registerHandler(this);
}
sp<AMessage> msg = new AMessage(kWhatPrepareAsync, this);
msg->post();
}
status_t NuPlayer::GenericSource::feedMoreTSData() {
return OK;
}
libstagefright/httplive/LiveSession.h & cpp
// static
// Bandwidth Switch Mark Defaults
const int64_t LiveSession::kUpSwitchMarkUs = 15000000ll;
const int64_t LiveSession::kDownSwitchMarkUs = 20000000ll;
const int64_t LiveSession::kUpSwitchMarginUs = 5000000ll;
const int64_t LiveSession::kResumeThresholdUs = 100000ll;
// Buffer Prepare/Ready/Underflow Marks
const int64_t LiveSession::kReadyMarkUs = 5000000ll;
const int64_t LiveSession::kPrepareMarkUs = 1500000ll;
const int64_t LiveSession::kUnderflowMarkUs = 1000000ll;
与Fetcher,Bandwidth Estimater(和ExoPlayer一样是滑动窗口平均),switching,Buffering相关的操作都在这里
nuplayer目录下
enum Flags {
// Don‘t log any URLs.不在log中记录URL
kFlagIncognito = 1,
};
NuPlayer::HTTPLiveSource::HTTPLiveSource(
if (headers) { //也搞了一个header机制
mExtraHeaders = *headers;
ssize_t index =
mExtraHeaders.indexOfKey(String8("x-hide-urls-from-log"));
if (index >= 0) {
mFlags |= kFlagIncognito;
mExtraHeaders.removeItemsAt(index);
}
}
}
---------------------------------------
void NuPlayer::HTTPLiveSource::prepareAsync() {
if (mLiveLooper == NULL) {
mLiveLooper = new ALooper;//一如既往的ALooper
mLiveLooper->setName("http live");
mLiveLooper->start();
mLiveLooper->registerHandler(this);
}
sp<AMessage> notify = new AMessage(kWhatSessionNotify, this);
mLiveSession = new LiveSession(
notify,
(mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0,
mHTTPService);
mLiveLooper->registerHandler(mLiveSession);
mLiveSession->connectAsync(//HTTPLiveSource包含LiveSession,很多实际的工作都由LiveSession完成
mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
}
ATSParser
frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
就是一个TS Parser,虽然也叫Axx,但是没有消息机制在里面
StreamingSource
nuplayer目录
void NuPlayer::StreamingSource::prepareAsync() {
if (mLooper == NULL) {
mLooper = new ALooper;
mLooper->setName("streaming");
mLooper->start();//何其相似
mLooper->registerHandler(this);
}
notifyVideoSizeChanged();
notifyFlagsChanged(0);
notifyPrepared();
}
---------------------------------------
StreamingSource中的数据由onReadBuffer驱动,最后的EOS,Discontiunity等都交给ATSParser去处理,ATSParser又最终交给AnotherPacketSource去做真正的处理.实际上,这里提到的三个Source最后都会用到AnotherPacketSource
void NuPlayer::StreamingSource::onReadBuffer() {
for (int32_t i = 0; i < kNumListenerQueuePackets; ++i) {
char buffer[188];
sp<AMessage> extra;
ssize_t n = mStreamListener->read(buffer, sizeof(buffer), &extra);//实际用NuPlayerStreamListener完成工作
if (n == 0) {
ALOGI("input data EOS reached.");
mTSParser->signalEOS(ERROR_END_OF_STREAM);//EOS了
setError(ERROR_END_OF_STREAM);
break;
} else if (n == INFO_DISCONTINUITY) {
int32_t type = ATSParser::DISCONTINUITY_TIME;
int32_t mask;
if (extra != NULL
&& extra->findInt32(
IStreamListener::kKeyDiscontinuityMask, &mask)) {
if (mask == 0) {
ALOGE("Client specified an illegal discontinuity type.");
setError(ERROR_UNSUPPORTED);
break;
}
type = mask;
}
mTSParser->signalDiscontinuity(
(ATSParser::DiscontinuityType)type, extra);
} else if (n < 0) {
break;
} else {
if (buffer[0] == 0x00) {
// XXX legacy
if (extra == NULL) {
extra = new AMessage;
}
uint8_t type = buffer[1];
if (type & 2) {
int64_t mediaTimeUs;
memcpy(&mediaTimeUs, &buffer[2], sizeof(mediaTimeUs));
extra->setInt64(IStreamListener::kKeyMediaTimeUs, mediaTimeUs);
}
mTSParser->signalDiscontinuity(
((type & 1) == 0)
? ATSParser::DISCONTINUITY_TIME
: ATSParser::DISCONTINUITY_FORMATCHANGE,
extra);
} else {
status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
if (err != OK) {
ALOGE("TS Parser returned error %d", err);
mTSParser->signalEOS(err);
setError(err);
break;
}
}
}
}
}
frameworks/av/media/libstagefright/mpeg2ts
可以类比ExoPlayer中的chunk source,同时负责buffer管理,EOS\Discontinuity的处理等等.前面三个Source最后都会落到AnotherPacketSource
bool AnotherPacketSource::hasBufferAvailable(status_t *finalResult) {
Mutex::Autolock autoLock(mLock);
*finalResult = OK;
if (!mEnabled) {
return false;
}
if (!mBuffers.empty()) {//一个ABuffer List,其实就是一个环形缓冲
return true;
}
*finalResult = mEOSResult;
return false;
}
--------------------------------------
void AnotherPacketSource::queueDiscontinuity(
ATSParser::DiscontinuityType type,
const sp<AMessage> &extra,
bool discard) {
Mutex::Autolock autoLock(mLock);
if (discard) {
// Leave only discontinuities in the queue.
…...
}
mEOSResult = OK;
mLastQueuedTimeUs = 0;
mLatestEnqueuedMeta = NULL;
if (type == ATSParser::DISCONTINUITY_NONE) {
return;
}
mDiscontinuitySegments.push_back(DiscontinuitySegment());
sp<ABuffer> buffer = new ABuffer(0);
buffer->meta()->setInt32("discontinuity", static_cast<int32_t>(type));
buffer->meta()->setMessage("extra", extra);
mBuffers.push_back(buffer); //将记录了discontinuity的ABuffer推入缓冲区中,这样各个Source在从缓冲区读数据的时候就能正确处理discontinuity了
mCondition.signal();
}
Decoder是如何被初始化的
从NuPlayer::OnStart方法看起
void NuPlayer::onStart(int64_t startPositionUs) {
if (!mSourceStarted) {
mSourceStarted = true;
mSource->start();
}
if (startPositionUs > 0) {
performSeek(startPositionUs);
if (mSource->getFormat(false /* audio */) == NULL) {
return;
}
}
...
sp<AMessage> notify = new AMessage(kWhatRendererNotify, this);
++mRendererGeneration;
notify->setInt32("generation", mRendererGeneration);
//在这里用AVNuFactory初始化Renderer和它对应的Looper
mRenderer = AVNuFactory::get()->createRenderer(mAudioSink, notify, flags);
mRendererLooper = new ALooper;
mRendererLooper->setName("NuPlayerRenderer");
mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
mRendererLooper->registerHandler(mRenderer);
//设置Renderer的播放参数
status_t err = mRenderer->setPlaybackSettings(mPlaybackSettings);
...
//给Decoder设置Renderer,两者之间的关系建立起来了
if (mVideoDecoder != NULL) {
mVideoDecoder->setRenderer(mRenderer);
}
if (mAudioDecoder != NULL) {
mAudioDecoder->setRenderer(mRenderer);
}
//抛出这一消息
postScanSources();
}
--------------------------------------
case kWhatScanSources:
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
if (generation != mScanSourcesGeneration) {
// Drop obsolete msg.
break;
}
mScanSourcesPending = false;
ALOGV("scanning sources haveAudio=%d, haveVideo=%d",
mAudioDecoder != NULL, mVideoDecoder != NULL);
bool mHadAnySourcesBefore =
(mAudioDecoder != NULL) || (mVideoDecoder != NULL);
bool rescan = false;
// initialize video before audio because successful initialization of
// video may change deep buffer mode of audio.
//在这里初始化decoder
if (mSurface != NULL) {
if (instantiateDecoder(false, &mVideoDecoder) == -EWOULDBLOCK) {
rescan = true;
}
}
// Don‘t try to re-open audio sink if there‘s an existing decoder.
if (mAudioSink != NULL && mAudioDecoder == NULL) {
if (instantiateDecoder(true, &mAudioDecoder) == -EWOULDBLOCK) {
rescan = true;
}
}
if (!mHadAnySourcesBefore
&& (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
// This is the first time we‘ve found anything playable.
if (mSourceFlags & Source::FLAG_DYNAMIC_DURATION) {
schedulePollDuration();
}
}
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;
}
//postScanSources和ExoPlayer中的doSomeWork一样是一个不断循环运转的东西
if (rescan) {
msg->post(100000ll);
mScanSourcesPending = true;
}
break;
}
---------------------------------------
在NuPlayer的instantiateDecoder中完成Decoder的初始化
status_t NuPlayer::instantiateDecoder(
bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
...
if (!audio) {
AString mime;
CHECK(format->findString("mime", &mime));
sp<AMessage> ccNotify = new AMessage(kWhatClosedCaptionNotify, this);
if (mCCDecoder == NULL) {
mCCDecoder = new CCDecoder(ccNotify); //new字幕解码器
}
if (mSourceFlags & Source::FLAG_SECURE) {
format->setInt32("secure", true);
}
if (mSourceFlags & Source::FLAG_PROTECTED) {
format->setInt32("protected", true);
}
float rate = getFrameRate();
if (rate > 0) {
format->setFloat("operating-rate", rate * mPlaybackSettings.mSpeed);
}
}
if (audio) {
sp<AMessage> notify = new AMessage(kWhatAudioNotify, this);
++mAudioDecoderGeneration;
notify->setInt32("generation", mAudioDecoderGeneration);
if (checkAudioModeChange) {
determineAudioModeChange(format);
}
if (mOffloadAudio)
mSource->setOffloadAudio(true /* offload */);
if (mOffloadAudio) {
const bool hasVideo = (mSource->getFormat(false /*audio */) != NULL);
format->setInt32("has-video", hasVideo);
*decoder = AVNuFactory::get()->createPassThruDecoder(notify, mSource, mRenderer);//利用AVNuFactory的方法创建pass through的音频解码器
} else {
AVNuUtils::get()->setCodecOutputFormat(format);
mSource->setOffloadAudio(false /* offload */);
*decoder = AVNuFactory::get()->createDecoder(notify, mSource, mPID, mRenderer);//创建普通的音频解码器
}
} else {
sp<AMessage> notify = new AMessage(kWhatVideoNotify, this);
++mVideoDecoderGeneration;
notify->setInt32("generation", mVideoDecoderGeneration);
*decoder = new Decoder(
notify, mSource, mPID, mRenderer, mSurface, mCCDecoder);
//new视频解码器,这里还会把字幕解码器作为一个参数传进来
// enable FRC if high-quality AV sync is requested, even if not
// directly queuing to display, as this will even improve textureview
// playback.
{
char value[PROPERTY_VALUE_MAX];
if (property_get("persist.sys.media.avsync", value, NULL) &&
(!strcmp("1", value) || !strcasecmp("true", value))) {
format->setInt32("auto-frc", 1);
}
}
}
(*decoder)->init();//解码器初始化
(*decoder)->configure(format);//解码器配置
// allocate buffers to decrypt widevine source buffers
if (!audio && (mSourceFlags & Source::FLAG_SECURE)) {
Vector<sp<ABuffer> > inputBufs;
CHECK_EQ((*decoder)->getInputBuffers(&inputBufs), (status_t)OK);
Vector<MediaBuffer *> mediaBufs;
for (size_t i = 0; i < inputBufs.size(); i++) {
const sp<ABuffer> &buffer = inputBufs[i];
MediaBuffer *mbuf = new MediaBuffer(buffer->data(), buffer->size());
mediaBufs.push(mbuf);
}
status_t err = mSource->setBuffers(audio, mediaBufs);
if (err != OK) {
for (size_t i = 0; i < mediaBufs.size(); ++i) {
mediaBufs[i]->release();
}
mediaBufs.clear();
ALOGE("Secure source didn‘t support secure mediaBufs.");
return err;
}
}
...
return OK;
}
-------------------------------------
-------------------------------------
void NuPlayer::Decoder::doFlush(bool notifyComplete) {
if (mCCDecoder != NULL) {
mCCDecoder->flush();//先flush字幕Decoder
}
if (mRenderer != NULL) {
mRenderer->flush(mIsAudio, notifyComplete);
mRenderer->signalTimeDiscontinuity();//再flush Renderer
}
status_t err = OK;
if (mCodec != NULL) {
err = mCodec->flush();//最后flush Decoder
mCSDsToSubmit = mCSDsForCurrentFormat; // copy operator
++mBufferGeneration;
}
...
releaseAndResetMediaBuffers();//清空buffer
mPaused = true;
}
NuPlayer::DecoderBase::DecoderBase(const sp<AMessage> ¬ify)
: mNotify(notify),
mBufferGeneration(0),
mPaused(false),
mStats(new AMessage),
mRequestInputBuffersPending(false) {
// Every decoder has its own looper because MediaCodec operations
// are blocking, but NuPlayer needs asynchronous operations.
mDecoderLooper = new ALooper;
mDecoderLooper->setName("NPDecoder");
mDecoderLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
}
void NuPlayer::DecoderBase::init() {
mDecoderLooper->registerHandler(this);
}
由相应的onMessage方法处理各种工作
先关注init和configure两个方法,init直接继承自DecoderBase,就是给Looper注册Handler
--------------------------------------
void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
...
mCodec = AVUtils::get()->createCustomComponentByName(mCodecLooper, mime.c_str(), false /* encoder */, format);
if (mCodec == NULL) {
//由mimeType创建Decoder
mCodec = MediaCodec::CreateByType(
mCodecLooper, mime.c_str(), false /* encoder */, NULL /* err */, mPid);
}
...
//mCodec就是libstagefright中的MediaCodec,没什么可说的
err = mCodec->configure(
format, mSurface, NULL /* crypto */, 0 /* flags */);
if (err != OK) {
ALOGE("Failed to configure %s decoder (err=%d)", mComponentName.c_str(), err);
mCodec->release();
mCodec.clear();
handleError(err);
return;
}
rememberCodecSpecificData(format);
// the following should work in configured state读Format信息
CHECK_EQ((status_t)OK, mCodec->getOutputFormat(&mOutputFormat));
CHECK_EQ((status_t)OK, mCodec->getInputFormat(&mInputFormat));
mStats->setString("mime", mime.c_str());
mStats->setString("component-name", mComponentName.c_str());
if (!mIsAudio) {
int32_t width, height;
if (mOutputFormat->findInt32("width", &width)
&& mOutputFormat->findInt32("height", &height)) {
mStats->setInt32("width", width);
mStats->setInt32("height", height);
}
}
...
//MediaCodec开始
err = mCodec->start();
if (err != OK) {
ALOGE("Failed to start %s decoder (err=%d)", mComponentName.c_str(), err);
mCodec->release();
mCodec.clear();
handleError(err);
return;
}
//先把buffer都release置为null
releaseAndResetMediaBuffers();
...
}
前面提到NuPlayer::Decoder里面有一个MediaCodec,所以不用再研究具体怎么解码的,关注点在于从这个模块输出的东西是怎样的,以及是怎样输入这个东西的
先来看输出:
当MediaCodec有Available Output的时候,在onMessageReceived中有
case MediaCodec::CB_OUTPUT_AVAILABLE:
{
int32_t index;
size_t offset;
size_t size;
int64_t timeUs;
int32_t flags;
CHECK(msg->findInt32("index", &index));
CHECK(msg->findSize("offset", &offset));
CHECK(msg->findSize("size", &size));
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("flags", &flags));
handleAnOutputBuffer(index, offset, size, timeUs, flags);
break;
}
-------------------------------------
bool NuPlayer::Decoder::handleAnOutputBuffer(
size_t index,
size_t offset,
size_t size,
int64_t timeUs,
int32_t flags) {
// CHECK_LT(bufferIx, mOutputBuffers.size());
sp<ABuffer> buffer;
mCodec->getOutputBuffer(index, &buffer);
....
//发送kWhatRenderBuffer消息
sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
reply->setSize("buffer-ix", index);
reply->setInt32("generation", mBufferGeneration);
if (eos) {
ALOGI("[%s] saw output EOS", mIsAudio ? "audio" : "video");
//EOS了
buffer->meta()->setInt32("eos", true);
reply->setInt32("eos", true);
} else if (mSkipRenderingUntilMediaTimeUs >= 0) {
if (timeUs < mSkipRenderingUntilMediaTimeUs) {
ALOGV("[%s] dropping buffer at time %lld as requested.",
mComponentName.c_str(), (long long)timeUs);
//中间这一段不用render,skip掉
reply->post();
return true;
}
mSkipRenderingUntilMediaTimeUs = -1;
} else if ((flags & MediaCodec::BUFFER_FLAG_DATACORRUPT) &&
AVNuUtils::get()->dropCorruptFrame()) {
ALOGV("[%s] dropping corrupt buffer at time %lld as requested.",
mComponentName.c_str(), (long long)timeUs);
//本段buffer坏到了,扔掉
reply->post();
return true;
}
mNumFramesTotal += !mIsAudio;
// wait until 1st frame comes out to signal resume complete
notifyResumeCompleteIfNecessary();
if (mRenderer != NULL) {
// send the buffer to renderer.把Buffer送到Renderer
mRenderer->queueBuffer(mIsAudio, buffer, reply);
if (eos && !isDiscontinuityPending()) {
mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
}
}
return true;
}
--------------------------------------
case kWhatRenderBuffer:
{
if (!isStaleReply(msg)) {
onRenderBuffer(msg);
}
break;
}
-------------------------------------
void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
...
if (!mIsAudio) {
int64_t timeUs;
sp<ABuffer> buffer = mOutputBuffers[bufferIx];
buffer->meta()->findInt64("timeUs", &timeUs);
if (mCCDecoder != NULL && mCCDecoder->isSelected()) {
mCCDecoder->display(timeUs);//字幕显示
}
}
if (msg->findInt32("render", &render) && render) {
int64_t timestampNs;
CHECK(msg->findInt64("timestampNs", ×tampNs));
//由MediaCodec的renderOutputBufferAndRelease完成
err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
} else {
mNumOutputFramesDropped += !mIsAudio;
err = mCodec->releaseOutputBuffer(bufferIx);
}
...
}
再来看输入
当MediaCodec有Available Input的时候,在onMessageReceived中有
case MediaCodec::CB_INPUT_AVAILABLE:
{
int32_t index;
CHECK(msg->findInt32("index", &index));
handleAnInputBuffer(index);
break;
}
------------------------------------
bool NuPlayer::Decoder::handleAnInputBuffer(size_t index) {
...
sp<ABuffer> buffer;
mCodec->getInputBuffer(index, &buffer);
...
if (index >= mInputBuffers.size()) {
for (size_t i = mInputBuffers.size(); i <= index; ++i) {
mInputBuffers.add();
mMediaBuffers.add();
mInputBufferIsDequeued.add();
mMediaBuffers.editItemAt(i) = NULL;
mInputBufferIsDequeued.editItemAt(i) = false;
}
}
mInputBuffers.editItemAt(index) = buffer;
//CHECK_LT(bufferIx, mInputBuffers.size());
if (mMediaBuffers[index] != NULL) {
mMediaBuffers[index]->release();
mMediaBuffers.editItemAt(index) = NULL;
}
mInputBufferIsDequeued.editItemAt(index) = true;
if (!mCSDsToSubmit.isEmpty()) {
sp<AMessage> msg = new AMessage();
msg->setSize("buffer-ix", index);
sp<ABuffer> buffer = mCSDsToSubmit.itemAt(0);
ALOGI("[%s] resubmitting CSD", mComponentName.c_str());
msg->setBuffer("buffer", buffer);
mCSDsToSubmit.removeAt(0);
if (!onInputBufferFetched(msg)) {
handleError(UNKNOWN_ERROR);
return false;
}
return true;
}
while (!mPendingInputMessages.empty()) {
sp<AMessage> msg = *mPendingInputMessages.begin();
if (!onInputBufferFetched(msg)) {
break;
}
mPendingInputMessages.erase(mPendingInputMessages.begin());
}
if (!mInputBufferIsDequeued.editItemAt(index)) {
return true;
}
mDequeuedInputBuffers.push_back(index);
onRequestInputBuffers();
return true;
}
------------------------------------
bool NuPlayer::Decoder::onInputBufferFetched(const sp<AMessage> &msg) {
...
sp<ABuffer> buffer;
bool hasBuffer = msg->findBuffer("buffer", &buffer);
// handle widevine classic source - that fills an arbitrary input buffer
MediaBuffer *mediaBuffer = NULL;
if (hasBuffer) {
//TODO:更多信息,可以研究mediabuffer
mediaBuffer = (MediaBuffer *)(buffer->getMediaBufferBase());
if (mediaBuffer != NULL) {
// likely filled another buffer than we requested: adjust buffer index
size_t ix;
for (ix = 0; ix < mInputBuffers.size(); ix++) {
const sp<ABuffer> &buf = mInputBuffers[ix];
if (buf->data() == mediaBuffer->data()) {
// all input buffers are dequeued on start, hence the check
if (!mInputBufferIsDequeued[ix]) {
ALOGV("[%s] received MediaBuffer for #%zu instead of #%zu",
mComponentName.c_str(), ix, bufferIx);
mediaBuffer->release();
return false;
}
// TRICKY: need buffer for the metadata, so instead, set
// codecBuffer to the same (though incorrect) buffer to
// avoid a memcpy into the codecBuffer
codecBuffer = buffer;
codecBuffer->setRange(
mediaBuffer->range_offset(),
mediaBuffer->range_length());
bufferIx = ix;
break;
}
}
CHECK(ix < mInputBuffers.size());
}
}
if (buffer == NULL /* includes !hasBuffer */) {
int32_t streamErr = ERROR_END_OF_STREAM;
CHECK(msg->findInt32("err", &streamErr) || !hasBuffer);
CHECK(streamErr != OK);
// attempt to queue EOS
status_t err = mCodec->queueInputBuffer(
bufferIx,
0,
0,
0,
MediaCodec::BUFFER_FLAG_EOS);
if (err == OK) {
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
} else if (streamErr == ERROR_END_OF_STREAM) {
streamErr = err;
// err will not be ERROR_END_OF_STREAM
}
if (streamErr != ERROR_END_OF_STREAM) {
ALOGE("Stream error for %s (err=%d), EOS %s queued",
mComponentName.c_str(),
streamErr,
err == OK ? "successfully" : "unsuccessfully");
handleError(streamErr);
}
} else {
sp<AMessage> extra;
if (buffer->meta()->findMessage("extra", &extra) && extra != NULL) {
int64_t resumeAtMediaTimeUs;
if (extra->findInt64(
"resume-at-mediaTimeUs", &resumeAtMediaTimeUs)) {
ALOGI("[%s] suppressing rendering until %lld us",
mComponentName.c_str(), (long long)resumeAtMediaTimeUs);
mSkipRenderingUntilMediaTimeUs = resumeAtMediaTimeUs;
}
}
int64_t timeUs = 0;
uint32_t flags = 0;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
int32_t eos, csd;
// we do not expect SYNCFRAME for decoder
if (buffer->meta()->findInt32("eos", &eos) && eos) {
flags |= MediaCodec::BUFFER_FLAG_EOS;
} else if (buffer->meta()->findInt32("csd", &csd) && csd) {
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
}
// copy into codec buffer
if (buffer != codecBuffer) {
if (buffer->size() > codecBuffer->capacity()) {
handleError(ERROR_BUFFER_TOO_SMALL);
mDequeuedInputBuffers.push_back(bufferIx);
return false;
}
codecBuffer->setRange(0, buffer->size());
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
}
status_t err = mCodec->queueInputBuffer(
bufferIx,
codecBuffer->offset(),
codecBuffer->size(),
timeUs,
flags);
if (err != OK) {
if (mediaBuffer != NULL) {
mediaBuffer->release();
}
ALOGE("Failed to queue input buffer for %s (err=%d)",
mComponentName.c_str(), err);
handleError(err);
} else {
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
if (mediaBuffer != NULL) {
CHECK(mMediaBuffers[bufferIx] == NULL);
mMediaBuffers.editItemAt(bufferIx) = mediaBuffer;
}
}
}
return true;
}不管做什么,最后都是MediaCodec.queueInputBuffer完成了实际工作
-------------------------------------
void NuPlayer::DecoderBase::onRequestInputBuffers() {
if (mRequestInputBuffersPending) {
return;
}
// doRequestBuffers() return true if we should request more data
if (doRequestBuffers()) {
mRequestInputBuffersPending = true;
//注意这里,会自己循环调用,不停地request
sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
msg->post(10 * 1000ll);
}
}
--------------------------------------
case kWhatRequestInputBuffers:
{
mRequestInputBuffersPending = false;
onRequestInputBuffers();
break;
}
--------------------------------------
/*
* returns true if we should request more data
*/
bool NuPlayer::Decoder::doRequestBuffers() {
// mRenderer is only NULL if we have a legacy widevine source that
// is not yet ready. In this case we must not fetch input.
if (isDiscontinuityPending() || mRenderer == NULL) {
return false;
}
status_t err = OK;
while (err == OK && !mDequeuedInputBuffers.empty()) {
size_t bufferIx = *mDequeuedInputBuffers.begin();
sp<AMessage> msg = new AMessage();
msg->setSize("buffer-ix", bufferIx);
err = fetchInputData(msg);
if (err != OK && err != ERROR_END_OF_STREAM) {
// if EOS, need to queue EOS buffer
break;
}
mDequeuedInputBuffers.erase(mDequeuedInputBuffers.begin());
if (!mPendingInputMessages.empty()
|| !onInputBufferFetched(msg)) { //前面分析过这个方法了
mPendingInputMessages.push_back(msg);
}
}
return err == -EWOULDBLOCK
&& mSource->feedMoreTSData() == OK;
}
-------------------------------------
status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) {
sp<ABuffer> accessUnit;
bool dropAccessUnit;
do {
//在这里调用source的方法,从而建立起了联系
status_t err = mSource->dequeueAccessUnit(mIsAudio, &accessUnit);
...
}while(...)
}
------------------------------------
至此,Decoder部分也分析完毕了
nuplayer目录下
Renderer的初始化在NuPlayer::OnStart方法中完成
void NuPlayer::onStart(int64_t startPositionUs) {
...
sp<AMessage> notify = new AMessage(kWhatRendererNotify, this);
++mRendererGeneration;
notify->setInt32("generation", mRendererGeneration);
mRenderer = AVNuFactory::get()->createRenderer(mAudioSink, notify, flags);
mRendererLooper = new ALooper;
mRendererLooper->setName("NuPlayerRenderer");
mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
mRendererLooper->registerHandler(mRenderer);
status_t err = mRenderer->setPlaybackSettings(mPlaybackSettings);
…...
float rate = getFrameRate();
if (rate > 0) {
mRenderer->setVideoFrameRate(rate);
}
if (mVideoDecoder != NULL) {
mVideoDecoder->setRenderer(mRenderer);
}
if (mAudioDecoder != NULL) {
mAudioDecoder->setRenderer(mRenderer);
}
postScanSources();
}
Renderer的数据输入在NuPlayer::Decoder::handleAnOutputBuffer中完成
bool NuPlayer::Decoder::handleAnOutputBuffer(
size_t index,
size_t offset,
size_t size,
int64_t timeUs,
int32_t flags) {
...
if (mRenderer != NULL) {
// send the buffer to renderer.把Buffer送到Renderer
mRenderer->queueBuffer(mIsAudio, buffer, reply);
if (eos && !isDiscontinuityPending()) {
mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
}
}
...
}
libmediaplayerservice/nuplayer/NuplayerRenderer.cpp
mRenderer->queueBuffer最终会调用下面的方法
void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) {
int32_t audio;
CHECK(msg->findInt32("audio", &audio));
if (dropBufferIfStale(audio, msg)) {
return;
}
if (audio) {
mHasAudio = true;
} else {
mHasVideo = true;
}
if (mHasVideo) {
if (mVideoScheduler == NULL) {
mVideoScheduler = new VideoFrameScheduler();//初始化VideoFrameSche,用于VSync
mVideoScheduler->init();
}
}
sp<ABuffer> buffer;
CHECK(msg->findBuffer("buffer", &buffer));
sp<AMessage> notifyConsumed;
CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed));
QueueEntry entry;//可以理解为buffer循环队列的抽象
entry.mBuffer = buffer;
entry.mNotifyConsumed = notifyConsumed;
entry.mOffset = 0;
entry.mFinalResult = OK;
entry.mBufferOrdinal = ++mTotalBuffersQueued;
//mAudioQueue和mVideoQueue都是List<QueueEntry>
if (audio) {
Mutex::Autolock autoLock(mLock);
mAudioQueue.push_back(entry);
postDrainAudioQueue_l();
} else {
mVideoQueue.push_back(entry);
postDrainVideoQueue();//每隔一段时间就被调用一次的
}
Mutex::Autolock autoLock(mLock);
if (!mSyncQueues || mAudioQueue.empty() || mVideoQueue.empty()) {
return;
}
sp<ABuffer> firstAudioBuffer = (*mAudioQueue.begin()).mBuffer;
sp<ABuffer> firstVideoBuffer = (*mVideoQueue.begin()).mBuffer;
if (firstAudioBuffer == NULL || firstVideoBuffer == NULL) {
// EOS signalled on either queue.某一个queue空了
syncQueuesDone_l();
return;
}
int64_t firstAudioTimeUs;
int64_t firstVideoTimeUs;
CHECK(firstAudioBuffer->meta()
->findInt64("timeUs", &firstAudioTimeUs));
CHECK(firstVideoBuffer->meta()
->findInt64("timeUs", &firstVideoTimeUs));
int64_t diff = firstVideoTimeUs - firstAudioTimeUs;
ALOGV("queueDiff = %.2f secs", diff / 1E6);
if (diff > 100000ll) {
// Audio data starts More than 0.1 secs before video.
// Drop some audio.音频超前视频0.1s,丢掉一些音频
(*mAudioQueue.begin()).mNotifyConsumed->post();
mAudioQueue.erase(mAudioQueue.begin());
return;
}
syncQueuesDone_l();
}
Renderer的数据输出
同样是在postDrainVideoQueue方法中,会抛出kwhatDrainVideoQueue的消息
void NuPlayer::Renderer::postDrainVideoQueue() {
….
sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, this);
msg->setInt32("drainGeneration", getDrainGeneration(false /* audio */));
…}
case kWhatDrainVideoQueue:
{
int32_t generation;
CHECK(msg->findInt32("drainGeneration", &generation));
if (generation != getDrainGeneration(false /* audio */)) {
break;
}
mDrainVideoQueuePending = false;
onDrainVideoQueue();
postDrainVideoQueue();
break;
}
void NuPlayer::Renderer::onDrainVideoQueue() {
...
QueueEntry *entry = &*mVideoQueue.begin();
if (entry->mBuffer == NULL) {
// EOS
notifyEOS(false /* audio */, entry->mFinalResult);
...
return;
}
int64_t nowUs = ALooper::GetNowUs();
int64_t realTimeUs;
int64_t mediaTimeUs = -1;
if (mFlags & FLAG_REAL_TIME) {
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &realTimeUs));
} else {
CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
}
bool tooLate = false;
if (!mPaused) {
//比较媒体时间和wall clock
setVideoLateByUs(nowUs - realTimeUs);
tooLate = (mVideoLateByUs > 40000);
if (tooLate) {
ALOGV("video late by %lld us (%.2f secs)",
(long long)mVideoLateByUs, mVideoLateByUs / 1E6);
} else {
int64_t mediaUs = 0;
mMediaClock->getMediaTime(realTimeUs, &mediaUs);
ALOGV("rendering video at media time %.2f secs",
(mFlags & FLAG_REAL_TIME ? realTimeUs :
mediaUs) / 1E6);...
}
} else {
setVideoLateByUs(0);
...
}
// Always render the first video frame while keeping stats on A/V sync.
if (!mVideoSampleReceived) {
realTimeUs = nowUs;
tooLate = false;
}
entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000ll);
entry->mNotifyConsumed->setInt32("render", !tooLate);
entry->mNotifyConsumed->post();
mVideoQueue.erase(mVideoQueue.begin());
entry = NULL;
mVideoSampleReceived = true;
if (!mPaused) {
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyVideoRenderingStart();//刚刚开始
}
Mutex::Autolock autoLock(mLock);
notifyIfMediaRenderingStarted_l();//started,和上面的notifyVideoRenderingStart走的基本是同一条路,都会post对应的kwhatxxxx msg,最后回到NuPlayer的onMessage中被处理
}
}
NuPlayer
case kWhatRendererNotify:
{
...
int32_t what;
CHECK(msg->findInt32("what", &what));
//根据what内容的不同能看到各种熟悉的消息
if (what == Renderer::kWhatEOS) {
...
if (audio) {
mAudioEOS = true;
} else {
mVideoEOS = true;
}
if (finalResult == ERROR_END_OF_STREAM) {
ALOGV("reached %s EOS", audio ? "audio" : "video");
} else {
ALOGE("%s track encountered an error (%d)",
audio ? "audio" : "video", finalResult);
notifyListener(
MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, finalResult);
}
if ((mAudioEOS || mAudioDecoder == NULL)
&& (mVideoEOS || mVideoDecoder == NULL)) {
notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
}
} else if (what == Renderer::kWhatFlushComplete) {
int32_t audio;
CHECK(msg->findInt32("audio", &audio));
if (audio) {
mAudioEOS = false;
} else {
mVideoEOS = false;
}
ALOGV("renderer %s flush completed.", audio ? "audio" : "video");
if (audio && (mFlushingAudio == NONE || mFlushingAudio == FLUSHED
|| mFlushingAudio == SHUT_DOWN)) {
// Flush has been handled by tear down.
break;
}
handleFlushComplete(audio, false /* isDecoder */);
finishFlushIfPossible();
} else if (what == Renderer::kWhatVideoRenderingStart) {
//对应前面的第一个,也就是刚刚开始render
notifyListener(MEDIA_INFO, MEDIA_INFO_RENDERING_START, 0);
} else if (what == Renderer::kWhatMediaRenderingStart) {
ALOGV("media rendering started");
//对应前面的第二个
notifyListener(MEDIA_STARTED, 0, 0);
} else if (what == Renderer::kWhatAudioTearDown) {
….
}
break;
}
framework\av\include\media
// The player just pushed the very first video frame for rendering
enum media_info_type {
MEDIA_INFO_RENDERING_START = 3,
}
enum media_event_type {
MEDIA_STARTED = 6,
}
至此,就完成对android中nuplayer的ahandler机制和source\decoder\renderer三个模块的分析。欢迎互相交流学习。
标签:method bst rand unknown create 输入 一段 取出 count
原文地址:http://blog.csdn.net/nonmarking/article/details/56957996