From f933441648ef6a71dee783d733aac17b9508b452 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 15 Dec 2010 15:17:42 -0800 Subject: Initial support for a true streaming player for mpeg2 transport streams. Change-Id: I153eec439d260a5524b21270e16d36940ec3161a --- media/libmediaplayerservice/Android.mk | 7 +- media/libmediaplayerservice/MediaPlayerService.cpp | 7 +- media/libmediaplayerservice/nuplayer/Android.mk | 22 + .../nuplayer/DecoderWrapper.cpp | 467 +++++++++++++++++++ .../nuplayer/DecoderWrapper.h | 81 ++++ media/libmediaplayerservice/nuplayer/NuPlayer.cpp | 477 ++++++++++++++++++++ media/libmediaplayerservice/nuplayer/NuPlayer.h | 108 +++++ .../nuplayer/NuPlayerDecoder.cpp | 286 ++++++++++++ .../nuplayer/NuPlayerDecoder.h | 64 +++ .../nuplayer/NuPlayerDriver.cpp | 136 ++++++ .../nuplayer/NuPlayerDriver.h | 69 +++ .../nuplayer/NuPlayerRenderer.cpp | 496 +++++++++++++++++++++ .../nuplayer/NuPlayerRenderer.h | 108 +++++ .../nuplayer/NuPlayerStreamListener.cpp | 151 +++++++ .../nuplayer/NuPlayerStreamListener.h | 74 +++ 15 files changed, 2550 insertions(+), 3 deletions(-) create mode 100644 media/libmediaplayerservice/nuplayer/Android.mk create mode 100644 media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp create mode 100644 media/libmediaplayerservice/nuplayer/DecoderWrapper.h create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayer.cpp create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayer.h create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerDriver.h create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp create mode 100644 media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h (limited to 'media/libmediaplayerservice') diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index a9d537f..f7f0d95 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -35,7 +35,8 @@ LOCAL_SHARED_LIBRARIES := \ libsurfaceflinger_client LOCAL_STATIC_LIBRARIES := \ - libstagefright_rtsp + libstagefright_rtsp \ + libstagefright_nuplayer \ ifneq ($(TARGET_SIMULATOR),true) LOCAL_SHARED_LIBRARIES += libdl @@ -47,9 +48,11 @@ LOCAL_C_INCLUDES := \ $(TOP)/frameworks/base/include/media/stagefright/openmax \ $(TOP)/frameworks/base/media/libstagefright/include \ $(TOP)/frameworks/base/media/libstagefright/rtsp \ - $(TOP)/external/tremolo/Tremolo + $(TOP)/external/tremolo/Tremolo \ LOCAL_MODULE:= libmediaplayerservice include $(BUILD_SHARED_LIBRARY) +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 63d09d6..6f011ce 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -58,6 +58,7 @@ #include "MidiFile.h" #include "TestPlayerStub.h" #include "StagefrightPlayer.h" +#include "nuplayer/NuPlayerDriver.h" #include @@ -759,6 +760,10 @@ static sp createPlayer(player_type playerType, void* cookie, LOGV(" create StagefrightPlayer"); p = new StagefrightPlayer; break; + case NU_PLAYER: + LOGV(" create NuPlayer"); + p = new NuPlayerDriver; + break; case TEST_PLAYER: LOGV("Create Test Player stub"); p = new TestPlayerStub(); @@ -887,7 +892,7 @@ status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64 status_t MediaPlayerService::Client::setDataSource( const sp &source) { // create the right type of player - sp p = createPlayer(STAGEFRIGHT_PLAYER); + sp p = createPlayer(NU_PLAYER); if (p == NULL) { return NO_INIT; diff --git a/media/libmediaplayerservice/nuplayer/Android.mk b/media/libmediaplayerservice/nuplayer/Android.mk new file mode 100644 index 0000000..c4f3764 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + NuPlayer.cpp \ + NuPlayerDecoder.cpp \ + NuPlayerDriver.cpp \ + NuPlayerRenderer.cpp \ + NuPlayerStreamListener.cpp \ + DecoderWrapper.cpp \ + +LOCAL_C_INCLUDES := \ + $(TOP)/frameworks/base/include/media/stagefright/openmax \ + $(TOP)/frameworks/base/media/libstagefright/include \ + $(TOP)/frameworks/base/media/libstagefright/mpeg2ts \ + +LOCAL_MODULE:= libstagefright_nuplayer + +LOCAL_MODULE_TAGS := eng + +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp b/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp new file mode 100644 index 0000000..89a5e69 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/DecoderWrapper.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "DecoderWrapper" +#include + +#include "DecoderWrapper.h" + +#include "AACDecoder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +struct DecoderWrapper::WrapperSource : public MediaSource { + WrapperSource( + const sp &meta, + const sp ¬ify); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + + void queueBuffer(const sp &buffer); + void queueEOS(status_t finalResult); + void clear(); + +protected: + virtual ~WrapperSource(); + +private: + Mutex mLock; + Condition mCondition; + + sp mMeta; + sp mNotify; + + List > mQueue; + status_t mFinalResult; + + DISALLOW_EVIL_CONSTRUCTORS(WrapperSource); +}; + +DecoderWrapper::WrapperSource::WrapperSource( + const sp &meta, const sp ¬ify) + : mMeta(meta), + mNotify(notify), + mFinalResult(OK) { +} + +DecoderWrapper::WrapperSource::~WrapperSource() { +} + +status_t DecoderWrapper::WrapperSource::start(MetaData *params) { + return OK; +} + +status_t DecoderWrapper::WrapperSource::stop() { + return OK; +} + +sp DecoderWrapper::WrapperSource::getFormat() { + return mMeta; +} + +status_t DecoderWrapper::WrapperSource::read( + MediaBuffer **out, const ReadOptions *options) { + Mutex::Autolock autoLock(mLock); + + bool requestedBuffer = false; + + while (mQueue.empty() && mFinalResult == OK) { + if (!requestedBuffer) { + mNotify->dup()->post(); + requestedBuffer = true; + } + + mCondition.wait(mLock); + } + + if (mQueue.empty()) { + return mFinalResult; + } + + sp src = *mQueue.begin(); + mQueue.erase(mQueue.begin()); + + MediaBuffer *dst = new MediaBuffer(src->size()); + memcpy(dst->data(), src->data(), src->size()); + + int64_t timeUs; + CHECK(src->meta()->findInt64("timeUs", &timeUs)); + + dst->meta_data()->setInt64(kKeyTime, timeUs); + + *out = dst; + + return OK; +} + +void DecoderWrapper::WrapperSource::queueBuffer(const sp &buffer) { + Mutex::Autolock autoLock(mLock); + mQueue.push_back(buffer); + mCondition.broadcast(); +} + +void DecoderWrapper::WrapperSource::queueEOS(status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + + Mutex::Autolock autoLock(mLock); + mFinalResult = finalResult; + mCondition.broadcast(); +} + +void DecoderWrapper::WrapperSource::clear() { + Mutex::Autolock autoLock(mLock); + mQueue.clear(); + mFinalResult = OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct DecoderWrapper::WrapperReader : public AHandler { + WrapperReader( + const sp &decoder, + const sp ¬ify); + + void start(); + void readMore(bool flush = false); + +protected: + virtual ~WrapperReader(); + + virtual void onMessageReceived(const sp &msg); + +private: + enum { + kWhatRead + }; + + sp mDecoder; + sp mNotify; + bool mEOS; + + DISALLOW_EVIL_CONSTRUCTORS(WrapperReader); +}; + +DecoderWrapper::WrapperReader::WrapperReader( + const sp &decoder, const sp ¬ify) + : mDecoder(decoder), + mNotify(notify), + mEOS(false) { +} + +DecoderWrapper::WrapperReader::~WrapperReader() { +} + +void DecoderWrapper::WrapperReader::start() { + CHECK_EQ(mDecoder->start(), (status_t)OK); + readMore(); +} + +void DecoderWrapper::WrapperReader::readMore(bool flush) { + if (!flush && mEOS) { + return; + } + + sp msg = new AMessage(kWhatRead, id()); + msg->setInt32("flush", static_cast(flush)); + msg->post(); +} + +void DecoderWrapper::WrapperReader::onMessageReceived( + const sp &msg) { + switch (msg->what()) { + case kWhatRead: + { + int32_t flush; + CHECK(msg->findInt32("flush", &flush)); + + MediaSource::ReadOptions options; + if (flush) { + // Dummy seek + options.setSeekTo(0); + mEOS = false; + } + + CHECK(!mEOS); + + MediaBuffer *src; + status_t err = mDecoder->read(&src, &options); + + sp notify = mNotify->dup(); + + sp realNotify; + CHECK(notify->findMessage("real-notify", &realNotify)); + + if (err == OK) { + realNotify->setInt32("what", ACodec::kWhatDrainThisBuffer); + + sp dst = new ABuffer(src->range_length()); + memcpy(dst->data(), + (const uint8_t *)src->data() + src->range_offset(), + src->range_length()); + + int64_t timeUs; + CHECK(src->meta_data()->findInt64(kKeyTime, &timeUs)); + src->release(); + src = NULL; + + dst->meta()->setInt64("timeUs", timeUs); + + realNotify->setObject("buffer", dst); + } else { + realNotify->setInt32("what", ACodec::kWhatEOS); + mEOS = true; + } + + notify->post(); + break; + } + + default: + TRESPASS(); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +DecoderWrapper::DecoderWrapper() + : mNumOutstandingInputBuffers(0), + mNumOutstandingOutputBuffers(0), + mNumPendingDecodes(0), + mFlushing(false) { +} + +DecoderWrapper::~DecoderWrapper() { +} + +void DecoderWrapper::setNotificationMessage(const sp &msg) { + mNotify = msg; +} + +void DecoderWrapper::initiateSetup(const sp &msg) { + msg->setWhat(kWhatSetup); + msg->setTarget(id()); + msg->post(); +} + +void DecoderWrapper::initiateShutdown() { + (new AMessage(kWhatShutdown, id()))->post(); +} + +void DecoderWrapper::signalFlush() { + (new AMessage(kWhatFlush, id()))->post(); +} + +void DecoderWrapper::signalResume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void DecoderWrapper::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatSetup: + onSetup(msg); + break; + + case kWhatInputDataRequested: + { + postFillBuffer(); + ++mNumOutstandingInputBuffers; + break; + } + + case kWhatInputBufferFilled: + { + CHECK_GT(mNumOutstandingInputBuffers, 0); + --mNumOutstandingInputBuffers; + + if (mFlushing) { + mSource->queueEOS(INFO_DISCONTINUITY); + + completeFlushIfPossible(); + break; + } + + sp obj; + if (!msg->findObject("buffer", &obj)) { + int32_t err = OK; + CHECK(msg->findInt32("err", &err)); + + mSource->queueEOS(err); + break; + } + + sp buffer = static_cast(obj.get()); + + mSource->queueBuffer(buffer); + break; + } + + case kWhatFillBufferDone: + { + CHECK_GT(mNumPendingDecodes, 0); + --mNumPendingDecodes; + + if (mFlushing) { + completeFlushIfPossible(); + break; + } + + sp notify; + CHECK(msg->findMessage("real-notify", ¬ify)); + + sp reply = + new AMessage(kWhatOutputBufferDrained, id()); + + notify->setMessage("reply", reply); + notify->post(); + + ++mNumOutstandingOutputBuffers; + break; + } + + case kWhatOutputBufferDrained: + { + CHECK_GT(mNumOutstandingOutputBuffers, 0); + --mNumOutstandingOutputBuffers; + + if (mFlushing) { + completeFlushIfPossible(); + break; + } + + ++mNumPendingDecodes; + mReader->readMore(); + break; + } + + case kWhatFlush: + { + onFlush(); + break; + } + + case kWhatResume: + { + onResume(); + break; + } + + default: + TRESPASS(); + break; + } +} + +void DecoderWrapper::onSetup(const sp &msg) { + AString mime; + CHECK(msg->findString("mime", &mime)); + + CHECK(!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)); + + int32_t numChannels, sampleRate; + CHECK(msg->findInt32("channel-count", &numChannels)); + CHECK(msg->findInt32("sample-rate", &sampleRate)); + + sp obj; + CHECK(msg->findObject("esds", &obj)); + sp esds = static_cast(obj.get()); + + sp meta = new MetaData; + meta->setCString(kKeyMIMEType, mime.c_str()); + meta->setInt32(kKeySampleRate, sampleRate); + meta->setInt32(kKeyChannelCount, numChannels); + meta->setData(kKeyESDS, 0, esds->data(), esds->size()); + + mSource = new WrapperSource( + meta, new AMessage(kWhatInputDataRequested, id())); + + sp decoder = new AACDecoder(mSource); + + mReaderLooper = new ALooper; + mReaderLooper->setName("DecoderWrapper looper"); + + mReaderLooper->start( + false, /* runOnCallingThread */ + false, /* canCallJava */ + PRIORITY_AUDIO); + + sp notify = new AMessage(kWhatFillBufferDone, id()); + notify->setMessage("real-notify", mNotify); + + mReader = new WrapperReader(decoder, notify); + mReaderLooper->registerHandler(mReader); + + mReader->start(); + ++mNumPendingDecodes; +} + +void DecoderWrapper::postFillBuffer() { + sp notify = mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFillThisBuffer); + sp reply = new AMessage(kWhatInputBufferFilled, id()); + notify->setMessage("reply", reply); + notify->post(); +} + +void DecoderWrapper::onFlush() { + CHECK(!mFlushing); + mFlushing = true; + + completeFlushIfPossible(); +} + +void DecoderWrapper::completeFlushIfPossible() { + CHECK(mFlushing); + + if (mNumOutstandingInputBuffers > 0 + || mNumOutstandingOutputBuffers > 0 + || mNumPendingDecodes > 0) { + return; + } + + mFlushing = false; + + sp notify = mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->post(); +} + +void DecoderWrapper::onResume() { + CHECK(!mFlushing); + + ++mNumPendingDecodes; + + mSource->clear(); + mReader->readMore(true /* flush */); +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/DecoderWrapper.h b/media/libmediaplayerservice/nuplayer/DecoderWrapper.h new file mode 100644 index 0000000..883b356 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/DecoderWrapper.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DECODER_WRAPPER_H_ + +#define DECODER_WRAPPER_H_ + +#include + +namespace android { + +struct MediaSource; + +struct DecoderWrapper : public AHandler { + DecoderWrapper(); + + void setNotificationMessage(const sp &msg); + void initiateSetup(const sp &msg); + void initiateShutdown(); + void signalFlush(); + void signalResume(); + +protected: + virtual ~DecoderWrapper(); + + virtual void onMessageReceived(const sp &msg); + +private: + struct WrapperSource; + struct WrapperReader; + + enum { + kWhatSetup, + kWhatInputBufferFilled, + kWhatOutputBufferDrained, + kWhatShutdown, + kWhatFillBufferDone, + kWhatInputDataRequested, + kWhatFlush, + kWhatResume, + }; + + sp mNotify; + + sp mSource; + + sp mReaderLooper; + sp mReader; + + int32_t mNumOutstandingInputBuffers; + int32_t mNumOutstandingOutputBuffers; + int32_t mNumPendingDecodes; + bool mFlushing; + + void onSetup(const sp &msg); + void onFlush(); + void onResume(); + + void postFillBuffer(); + void completeFlushIfPossible(); + + DISALLOW_EVIL_CONSTRUCTORS(DecoderWrapper); +}; + +} // namespace android + +#endif // DECODER_WRAPPER_H_ + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp new file mode 100644 index 0000000..403029a --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayer" +#include + +#include "NuPlayer.h" +#include "NuPlayerDecoder.h" +#include "NuPlayerRenderer.h" +#include "NuPlayerStreamListener.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +NuPlayer::NuPlayer() + : mEOS(false), + mAudioEOS(false), + mVideoEOS(false), + mFlushingAudio(NONE), + mFlushingVideo(NONE) { +} + +NuPlayer::~NuPlayer() { +} + +void NuPlayer::setListener(const wp &listener) { + mListener = listener; +} + +void NuPlayer::setDataSource(const sp &source) { + sp msg = new AMessage(kWhatSetDataSource, id()); + + source->incStrong(this); + msg->setPointer("source", source.get()); // XXX unsafe. + + msg->post(); +} + +void NuPlayer::setVideoSurface(const sp &surface) { + sp msg = new AMessage(kWhatSetVideoSurface, id()); + msg->setObject("surface", surface); + msg->post(); +} + +void NuPlayer::setAudioSink(const sp &sink) { + sp msg = new AMessage(kWhatSetAudioSink, id()); + msg->setObject("sink", sink); + msg->post(); +} + +void NuPlayer::start() { + (new AMessage(kWhatStart, id()))->post(); +} + +void NuPlayer::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatSetDataSource: + { + LOGI("kWhatSetDataSource"); + + CHECK(mSource == NULL); + + void *ptr; + CHECK(msg->findPointer("source", &ptr)); + + mSource = static_cast(ptr); + mSource->decStrong(this); + + mStreamListener = new NuPlayerStreamListener(mSource, id()); + mTSParser = new ATSParser; + break; + } + + case kWhatSetVideoSurface: + { + LOGI("kWhatSetVideoSurface"); + + sp obj; + CHECK(msg->findObject("surface", &obj)); + + mSurface = static_cast(obj.get()); + break; + } + + case kWhatSetAudioSink: + { + LOGI("kWhatSetAudioSink"); + + sp obj; + CHECK(msg->findObject("sink", &obj)); + + mAudioSink = static_cast(obj.get()); + break; + } + + case kWhatStart: + { + mStreamListener->start(); + + mRenderer = new Renderer( + mAudioSink, + new AMessage(kWhatRendererNotify, id())); + + looper()->registerHandler(mRenderer); + + (new AMessage(kWhatScanSources, id()))->post(); + break; + } + + case kWhatScanSources: + { + instantiateDecoder(false, &mVideoDecoder); + + if (mAudioSink != NULL) { + instantiateDecoder(true, &mAudioDecoder); + } + + if (mEOS) { + break; + } + + feedMoreTSData(); + + if (mAudioDecoder == NULL || mVideoDecoder == NULL) { + msg->post(100000ll); + } + break; + } + + case kWhatVideoNotify: + case kWhatAudioNotify: + { + bool audio = msg->what() == kWhatAudioNotify; + + sp codecRequest; + CHECK(msg->findMessage("codec-request", &codecRequest)); + + int32_t what; + CHECK(codecRequest->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + status_t err = feedDecoderInputData( + audio, codecRequest); + + if (err == -EWOULDBLOCK && !mEOS) { + feedMoreTSData(); + msg->post(); + } + } else if (what == ACodec::kWhatEOS) { + mRenderer->queueEOS(audio, ERROR_END_OF_STREAM); + } else if (what == ACodec::kWhatFlushCompleted) { + if (audio) { + CHECK_EQ((int)mFlushingAudio, (int)FLUSHING_DECODER); + mFlushingAudio = FLUSHED; + } else { + CHECK_EQ((int)mFlushingVideo, (int)FLUSHING_DECODER); + mFlushingVideo = FLUSHED; + } + + LOGI("decoder %s flush completed", audio ? "audio" : "video"); + + if (mFlushingAudio == FLUSHED && mFlushingVideo == FLUSHED) { + LOGI("both audio and video are flushed now."); + + mRenderer->signalTimeDiscontinuity(); + + if (mAudioDecoder != NULL) { + mAudioDecoder->signalResume(); + } + + if (mVideoDecoder != NULL) { + mVideoDecoder->signalResume(); + } + + mFlushingAudio = NONE; + mFlushingVideo = NONE; + } + } else { + CHECK_EQ((int)what, (int)ACodec::kWhatDrainThisBuffer); + + renderBuffer(audio, codecRequest); + } + + break; + } + + case kWhatRendererNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == Renderer::kWhatEOS) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (audio) { + mAudioEOS = true; + } else { + mVideoEOS = true; + } + + LOGI("reached %s EOS", audio ? "audio" : "video"); + + if ((mAudioEOS || mAudioDecoder == NULL) + && (mVideoEOS || mVideoDecoder == NULL)) { + notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0); + } + } else { + CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete); + + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + LOGI("renderer %s flush completed.", audio ? "audio" : "video"); + } + break; + } + + case kWhatMoreDataQueued: + { + break; + } + + default: + TRESPASS(); + break; + } +} + +void NuPlayer::feedMoreTSData() { + CHECK(!mEOS); + + for (int32_t i = 0; i < 10; ++i) { + char buffer[188]; + ssize_t n = mStreamListener->read(buffer, sizeof(buffer)); + + if (n == 0) { + LOGI("input data EOS reached."); + mTSParser->signalEOS(ERROR_END_OF_STREAM); + mEOS = true; + break; + } else if (n == INFO_DISCONTINUITY) { + mTSParser->signalDiscontinuity(ATSParser::DISCONTINUITY_SEEK); + } else if (n < 0) { + CHECK_EQ(n, -EWOULDBLOCK); + break; + } else { + if (buffer[0] == 0x00) { + // XXX legacy + mTSParser->signalDiscontinuity(ATSParser::DISCONTINUITY_SEEK); + } else { + mTSParser->feedTSPacket(buffer, sizeof(buffer)); + } + } + } +} + +status_t NuPlayer::dequeueNextAccessUnit( + ATSParser::SourceType *type, sp *accessUnit) { + accessUnit->clear(); + + status_t audioErr = -EWOULDBLOCK; + int64_t audioTimeUs; + + sp audioSource = + static_cast( + mTSParser->getSource(ATSParser::MPEG2ADTS_AUDIO).get()); + + if (audioSource != NULL) { + audioErr = audioSource->nextBufferTime(&audioTimeUs); + } + + status_t videoErr = -EWOULDBLOCK; + int64_t videoTimeUs; + + sp videoSource = + static_cast( + mTSParser->getSource(ATSParser::AVC_VIDEO).get()); + + if (videoSource != NULL) { + videoErr = videoSource->nextBufferTime(&videoTimeUs); + } + + if (audioErr == -EWOULDBLOCK || videoErr == -EWOULDBLOCK) { + return -EWOULDBLOCK; + } + + if (audioErr != OK && videoErr != OK) { + return audioErr; + } + + if (videoErr != OK || (audioErr == OK && audioTimeUs < videoTimeUs)) { + *type = ATSParser::MPEG2ADTS_AUDIO; + return audioSource->dequeueAccessUnit(accessUnit); + } else { + *type = ATSParser::AVC_VIDEO; + return videoSource->dequeueAccessUnit(accessUnit); + } +} + +status_t NuPlayer::dequeueAccessUnit( + ATSParser::SourceType type, sp *accessUnit) { + sp source = + static_cast(mTSParser->getSource(type).get()); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + status_t finalResult; + if (!source->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EWOULDBLOCK : finalResult; + } + + return source->dequeueAccessUnit(accessUnit); +} + +status_t NuPlayer::instantiateDecoder( + bool audio, sp *decoder) { + if (*decoder != NULL) { + return OK; + } + + ATSParser::SourceType type = + audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO; + + sp source = + static_cast( + mTSParser->getSource(type).get()); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + sp notify = + new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify, + id()); + + *decoder = new Decoder(notify, audio ? NULL : mSurface); + looper()->registerHandler(*decoder); + + const sp &meta = source->getFormat(); + (*decoder)->configure(meta); + + if (audio) { + int32_t sampleRate; + int32_t channelCount; + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + CHECK(meta->findInt32(kKeyChannelCount, &channelCount)); + + channelCount = 2; // XXX + + CHECK_EQ(mAudioSink->open(sampleRate, channelCount), (status_t)OK); + mAudioSink->start(); + } + + return OK; +} + +status_t NuPlayer::feedDecoderInputData(bool audio, const sp &msg) { + sp reply; + CHECK(msg->findMessage("reply", &reply)); + + if ((audio && mFlushingAudio == FLUSHING_DECODER) + || (!audio && mFlushingVideo == FLUSHING_DECODER)) { + reply->setInt32("err", INFO_DISCONTINUITY); + reply->post(); + return OK; + } + + sp accessUnit; + status_t err = dequeueAccessUnit( + audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO, + &accessUnit); + + if (err == -EWOULDBLOCK) { + return err; + } else if (err != OK) { + if (err == INFO_DISCONTINUITY) { + LOGI("%s discontinuity", audio ? "audio" : "video"); + (audio ? mAudioDecoder : mVideoDecoder)->signalFlush(); + mRenderer->flush(audio); + + if (audio) { + CHECK(mFlushingAudio == NONE + || mFlushingAudio == AWAITING_DISCONTINUITY); + mFlushingAudio = FLUSHING_DECODER; + if (mFlushingVideo == NONE) { + mFlushingVideo = (mVideoDecoder != NULL) + ? AWAITING_DISCONTINUITY + : FLUSHED; + } + } else { + CHECK(mFlushingVideo == NONE + || mFlushingVideo == AWAITING_DISCONTINUITY); + mFlushingVideo = FLUSHING_DECODER; + if (mFlushingAudio == NONE) { + mFlushingAudio = (mAudioDecoder != NULL) + ? AWAITING_DISCONTINUITY + : FLUSHED; + } + } + } + + reply->setInt32("err", err); + reply->post(); + return OK; + } + + LOGV("returned a valid buffer of %s data", audio ? "audio" : "video"); + +#if 0 + int64_t mediaTimeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs)); + LOGI("feeding %s input buffer at media time %.2f secs", + audio ? "audio" : "video", + mediaTimeUs / 1E6); +#endif + + reply->setObject("buffer", accessUnit); + reply->post(); + + return OK; +} + +void NuPlayer::renderBuffer(bool audio, const sp &msg) { + LOGV("renderBuffer %s", audio ? "audio" : "video"); + + sp reply; + CHECK(msg->findMessage("reply", &reply)); + + sp obj; + CHECK(msg->findObject("buffer", &obj)); + + sp buffer = static_cast(obj.get()); + + mRenderer->queueBuffer(audio, buffer, reply); +} + +void NuPlayer::notifyListener(int msg, int ext1, int ext2) { + if (mListener == NULL) { + return; + } + + sp listener = mListener.promote(); + + if (listener == NULL) { + return; + } + + listener->sendEvent(msg, ext1, ext2); +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h new file mode 100644 index 0000000..9a5a6c4 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NU_PLAYER_H_ + +#define NU_PLAYER_H_ + +#include +#include + +#include "ATSParser.h" +#include "AnotherPacketSource.h" + +namespace android { + +struct ACodec; +struct MetaData; + +struct NuPlayer : public AHandler { + NuPlayer(); + + void setListener(const wp &listener); + + void setDataSource(const sp &source); + void setVideoSurface(const sp &surface); + void setAudioSink(const sp &sink); + void start(); + +protected: + virtual ~NuPlayer(); + + virtual void onMessageReceived(const sp &msg); + +private: + struct Renderer; + struct Decoder; + struct NuPlayerStreamListener; + + enum { + kWhatSetDataSource, + kWhatSetVideoSurface, + kWhatSetAudioSink, + kWhatMoreDataQueued, + kWhatStart, + kWhatScanSources, + kWhatVideoNotify, + kWhatAudioNotify, + kWhatRendererNotify, + }; + + wp mListener; + sp mSource; + sp mSurface; + sp mAudioSink; + sp mStreamListener; + sp mTSParser; + sp mVideoDecoder; + sp mAudioDecoder; + sp mRenderer; + + bool mEOS; + bool mAudioEOS; + bool mVideoEOS; + + enum FlushStatus { + NONE, + AWAITING_DISCONTINUITY, + FLUSHING_DECODER, + FLUSHED + }; + + FlushStatus mFlushingAudio; + FlushStatus mFlushingVideo; + + status_t instantiateDecoder( + bool audio, sp *decoder); + + status_t feedDecoderInputData(bool audio, const sp &msg); + void renderBuffer(bool audio, const sp &msg); + + status_t dequeueNextAccessUnit( + ATSParser::SourceType *type, sp *accessUnit); + + status_t dequeueAccessUnit( + ATSParser::SourceType type, sp *accessUnit); + + void feedMoreTSData(); + void notifyListener(int msg, int ext1, int ext2); + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayer); +}; + +} // namespace android + +#endif // NU_PLAYER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp new file mode 100644 index 0000000..d1ed222 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayerDecoder" +#include + +#include "NuPlayerDecoder.h" + +#include "DecoderWrapper.h" +#include "ESDS.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +NuPlayer::Decoder::Decoder( + const sp ¬ify, const sp &surface) + : mNotify(notify), + mSurface(surface) { +} + +NuPlayer::Decoder::~Decoder() { +} + +void NuPlayer::Decoder::configure(const sp &meta) { + CHECK(mCodec == NULL); + CHECK(mWrapper == NULL); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp notifyMsg = + new AMessage(kWhatCodecNotify, id()); + + sp format = makeFormat(meta); + + if (mSurface != NULL) { + format->setObject("surface", mSurface); + } + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + mWrapper = new DecoderWrapper; + looper()->registerHandler(mWrapper); + + mWrapper->setNotificationMessage(notifyMsg); + mWrapper->initiateSetup(format); + } else { + mCodec = new ACodec; + looper()->registerHandler(mCodec); + + mCodec->setNotificationMessage(notifyMsg); + mCodec->initiateSetup(format); + } +} + +void NuPlayer::Decoder::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + onFillThisBuffer(msg); + } else { + sp notify = mNotify->dup(); + notify->setMessage("codec-request", msg); + notify->post(); + } + break; + } + + default: + TRESPASS(); + break; + } +} + +sp NuPlayer::Decoder::makeFormat(const sp &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp msg = new AMessage; + msg->setString("mime", mime); + + if (!strncasecmp("video/", mime, 6)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + msg->setInt32("width", width); + msg->setInt32("height", height); + } else { + CHECK(!strncasecmp("audio/", mime, 6)); + + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + msg->setInt32("channel-count", numChannels); + msg->setInt32("sample-rate", sampleRate); + } + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + // Parse the AVCDecoderConfigurationRecord + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + + // There is decodable content out there that fails the following + // assertion, let's be lenient for now... + // CHECK((ptr[4] >> 2) == 0x3f); // reserved + + size_t lengthSize = 1 + (ptr[4] & 3); + + // commented out check below as H264_QVGA_500_NO_AUDIO.3gp + // violates it... + // CHECK((ptr[5] >> 5) == 7); // reserved + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + sp buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + CHECK(size >= 1); + size_t numPictureParameterSets = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPictureParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + msg->setObject("csd", buffer); + } else if (meta->findData(kKeyESDS, &type, &data, &size)) { +#if 0 + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + sp buffer = new ABuffer(codec_specific_data_size); + + memcpy(buffer->data(), codec_specific_data, + codec_specific_data_size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); +#else + sp buffer = new ABuffer(size); + memcpy(buffer->data(), data, size); + + msg->setObject("esds", buffer); +#endif + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + mCSDIndex = 0; + + return msg; +} + +void NuPlayer::Decoder::onFillThisBuffer(const sp &msg) { + sp reply; + CHECK(msg->findMessage("reply", &reply)); + +#if 0 + sp obj; + CHECK(msg->findObject("buffer", &obj)); + sp outBuffer = static_cast(obj.get()); +#else + sp outBuffer; +#endif + + if (mCSDIndex < mCSD.size()) { + outBuffer = mCSD.editItemAt(mCSDIndex++); + outBuffer->meta()->setInt64("timeUs", 0); + + reply->setObject("buffer", outBuffer); + reply->post(); + return; + } + + sp notify = mNotify->dup(); + notify->setMessage("codec-request", msg); + notify->post(); +} + +void NuPlayer::Decoder::signalFlush() { + if (mCodec != NULL) { + mCodec->signalFlush(); + } else { + CHECK(mWrapper != NULL); + mWrapper->signalFlush(); + } +} + +void NuPlayer::Decoder::signalResume() { + if (mCodec != NULL) { + mCodec->signalResume(); + } else { + CHECK(mWrapper != NULL); + mWrapper->signalResume(); + } +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h new file mode 100644 index 0000000..77800be --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NUPLAYER_DECODER_H_ + +#define NUPLAYER_DECODER_H_ + +#include "NuPlayer.h" + +#include + +namespace android { + +struct DecoderWrapper; + +struct NuPlayer::Decoder : public AHandler { + Decoder(const sp ¬ify, const sp &surface = NULL); + + void configure(const sp &meta); + void signalFlush(); + void signalResume(); + +protected: + virtual ~Decoder(); + + virtual void onMessageReceived(const sp &msg); + +private: + enum { + kWhatCodecNotify, + }; + + sp mNotify; + sp mSurface; + + sp mCodec; + sp mWrapper; + + Vector > mCSD; + size_t mCSDIndex; + + sp makeFormat(const sp &meta); + + void onFillThisBuffer(const sp &msg); + + DISALLOW_EVIL_CONSTRUCTORS(Decoder); +}; + +} // namespace android + +#endif // NUPLAYER_DECODER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp new file mode 100644 index 0000000..b79251a --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayerDriver" +#include + +#include "NuPlayerDriver.h" + +#include "NuPlayer.h" + +#include + +namespace android { + +NuPlayerDriver::NuPlayerDriver() + : mLooper(new ALooper) { + mLooper->setName("NuPlayerDriver Looper"); + + mLooper->start( + false, /* runOnCallingThread */ + true, /* canCallJava */ + PRIORITY_AUDIO); + + mPlayer = new NuPlayer; + mLooper->registerHandler(mPlayer); + + mPlayer->setListener(this); +} + +NuPlayerDriver::~NuPlayerDriver() { + mLooper->stop(); +} + +status_t NuPlayerDriver::initCheck() { + return OK; +} + +status_t NuPlayerDriver::setDataSource( + const char *url, const KeyedVector *headers) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::setDataSource(const sp &source) { + mPlayer->setDataSource(source); + + return OK; +} + +status_t NuPlayerDriver::setVideoSurface(const sp &surface) { + mPlayer->setVideoSurface(surface); + + return OK; +} + +status_t NuPlayerDriver::prepare() { + return OK; +} + +status_t NuPlayerDriver::prepareAsync() { + return OK; +} + +status_t NuPlayerDriver::start() { + mPlayer->start(); + + return OK; +} + +status_t NuPlayerDriver::stop() { + return OK; +} + +status_t NuPlayerDriver::pause() { + return OK; +} + +bool NuPlayerDriver::isPlaying() { + return false; +} + +status_t NuPlayerDriver::seekTo(int msec) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::getCurrentPosition(int *msec) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::getDuration(int *msec) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::reset() { + return OK; +} + +status_t NuPlayerDriver::setLooping(int loop) { + return INVALID_OPERATION; +} + +player_type NuPlayerDriver::playerType() { + return NU_PLAYER; +} + +status_t NuPlayerDriver::invoke(const Parcel &request, Parcel *reply) { + return INVALID_OPERATION; +} + +void NuPlayerDriver::setAudioSink(const sp &audioSink) { + mPlayer->setAudioSink(audioSink); +} + +status_t NuPlayerDriver::getMetadata( + const media::Metadata::Filter& ids, Parcel *records) { + return INVALID_OPERATION; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h new file mode 100644 index 0000000..245f1dd --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android { + +struct ALooper; +struct NuPlayer; + +struct NuPlayerDriver : public MediaPlayerInterface { + NuPlayerDriver(); + + virtual status_t initCheck(); + + virtual status_t setDataSource( + const char *url, const KeyedVector *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual status_t setDataSource(const sp &source); + + virtual status_t setVideoSurface(const sp &surface); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t invoke(const Parcel &request, Parcel *reply); + virtual void setAudioSink(const sp &audioSink); + + virtual status_t getMetadata( + const media::Metadata::Filter& ids, Parcel *records); + +protected: + virtual ~NuPlayerDriver(); + +private: + sp mLooper; + sp mPlayer; + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayerDriver); +}; + +} // namespace android + + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp new file mode 100644 index 0000000..855bc0a --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayerRenderer" +#include + +#include "NuPlayerRenderer.h" + +#include +#include + +namespace android { + +NuPlayer::Renderer::Renderer( + const sp &sink, + const sp ¬ify) + : mAudioSink(sink), + mNotify(notify), + mNumFramesWritten(0), + mDrainAudioQueuePending(false), + mDrainVideoQueuePending(false), + mAudioQueueGeneration(0), + mVideoQueueGeneration(0), + mAnchorTimeMediaUs(-1), + mAnchorTimeRealUs(-1), + mFlushingAudio(false), + mFlushingVideo(false), + mSyncQueues(true) { +} + +NuPlayer::Renderer::~Renderer() { +} + +void NuPlayer::Renderer::queueBuffer( + bool audio, + const sp &buffer, + const sp ¬ifyConsumed) { + sp msg = new AMessage(kWhatQueueBuffer, id()); + msg->setInt32("audio", static_cast(audio)); + msg->setObject("buffer", buffer); + msg->setMessage("notifyConsumed", notifyConsumed); + msg->post(); +} + +void NuPlayer::Renderer::queueEOS(bool audio, status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + + sp msg = new AMessage(kWhatQueueEOS, id()); + msg->setInt32("audio", static_cast(audio)); + msg->setInt32("finalResult", finalResult); + msg->post(); +} + +void NuPlayer::Renderer::flush(bool audio) { + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + CHECK(!mFlushingAudio); + mFlushingAudio = true; + } else { + CHECK(!mFlushingVideo); + mFlushingVideo = true; + } + } + + sp msg = new AMessage(kWhatFlush, id()); + msg->setInt32("audio", static_cast(audio)); + msg->post(); +} + +void NuPlayer::Renderer::signalTimeDiscontinuity() { + CHECK(mAudioQueue.empty()); + CHECK(mVideoQueue.empty()); + mAnchorTimeMediaUs = -1; + mAnchorTimeRealUs = -1; + mSyncQueues = true; +} + +void NuPlayer::Renderer::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatDrainAudioQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mAudioQueueGeneration) { + break; + } + + mDrainAudioQueuePending = false; + + onDrainAudioQueue(); + + postDrainAudioQueue(); + break; + } + + case kWhatDrainVideoQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mVideoQueueGeneration) { + break; + } + + mDrainVideoQueuePending = false; + + onDrainVideoQueue(); + + postDrainVideoQueue(); + break; + } + + case kWhatQueueBuffer: + { + onQueueBuffer(msg); + break; + } + + case kWhatQueueEOS: + { + onQueueEOS(msg); + break; + } + + case kWhatFlush: + { + onFlush(msg); + break; + } + + default: + TRESPASS(); + break; + } +} + +void NuPlayer::Renderer::postDrainAudioQueue() { + if (mDrainAudioQueuePending || mSyncQueues) { + return; + } + + if (mAudioQueue.empty()) { + return; + } + + mDrainAudioQueuePending = true; + sp msg = new AMessage(kWhatDrainAudioQueue, id()); + msg->setInt32("generation", mAudioQueueGeneration); + msg->post(10000); +} + +void NuPlayer::Renderer::onDrainAudioQueue() { + uint32_t numFramesPlayed; + CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), (status_t)OK); + + ssize_t numFramesAvailableToWrite = + mAudioSink->frameCount() - (mNumFramesWritten - numFramesPlayed); + + CHECK_GE(numFramesAvailableToWrite, 0); + + size_t numBytesAvailableToWrite = + numFramesAvailableToWrite * mAudioSink->frameSize(); + + while (numBytesAvailableToWrite > 0) { + if (mAudioQueue.empty()) { + break; + } + + QueueEntry *entry = &*mAudioQueue.begin(); + + if (entry->mBuffer == NULL) { + // EOS + + notifyEOS(true /* audio */); + + mAudioQueue.erase(mAudioQueue.begin()); + entry = NULL; + return; + } + + if (entry->mOffset == 0) { + int64_t mediaTimeUs; + CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + LOGV("rendering audio at media time %.2f secs", mediaTimeUs / 1E6); + + mAnchorTimeMediaUs = mediaTimeUs; + + uint32_t numFramesPlayed; + CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), (status_t)OK); + + uint32_t numFramesPendingPlayout = + mNumFramesWritten - numFramesPlayed; + + int64_t realTimeOffsetUs = + (mAudioSink->latency() / 2 /* XXX */ + + numFramesPendingPlayout + * mAudioSink->msecsPerFrame()) * 1000ll; + + // LOGI("realTimeOffsetUs = %lld us", realTimeOffsetUs); + + mAnchorTimeRealUs = + ALooper::GetNowUs() + realTimeOffsetUs; + } + + size_t copy = entry->mBuffer->size() - entry->mOffset; + if (copy > numBytesAvailableToWrite) { + copy = numBytesAvailableToWrite; + } + + CHECK_EQ(mAudioSink->write( + entry->mBuffer->data() + entry->mOffset, copy), + (ssize_t)copy); + + entry->mOffset += copy; + if (entry->mOffset == entry->mBuffer->size()) { + entry->mNotifyConsumed->post(); + mAudioQueue.erase(mAudioQueue.begin()); + entry = NULL; + } + + numBytesAvailableToWrite -= copy; + mNumFramesWritten += copy / mAudioSink->frameSize(); + } +} + +void NuPlayer::Renderer::postDrainVideoQueue() { + if (mDrainVideoQueuePending || mSyncQueues) { + return; + } + + if (mVideoQueue.empty()) { + return; + } + + QueueEntry &entry = *mVideoQueue.begin(); + + sp msg = new AMessage(kWhatDrainVideoQueue, id()); + msg->setInt32("generation", mVideoQueueGeneration); + + int64_t delayUs; + + if (entry.mBuffer == NULL) { + // EOS doesn't carry a timestamp. + delayUs = 0; + } else { + int64_t mediaTimeUs; + CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + if (mAnchorTimeMediaUs < 0) { + delayUs = 0; + + if (mAudioSink == NULL) { + mAnchorTimeMediaUs = mediaTimeUs; + mAnchorTimeRealUs = ALooper::GetNowUs(); + } + } else { + int64_t realTimeUs = + (mediaTimeUs - mAnchorTimeMediaUs) + mAnchorTimeRealUs; + + delayUs = realTimeUs - ALooper::GetNowUs(); + } + } + + msg->post(delayUs); + + mDrainVideoQueuePending = true; +} + +void NuPlayer::Renderer::onDrainVideoQueue() { + if (mVideoQueue.empty()) { + return; + } + + QueueEntry *entry = &*mVideoQueue.begin(); + + if (entry->mBuffer == NULL) { + // EOS + + notifyEOS(false /* audio */); + + mVideoQueue.erase(mVideoQueue.begin()); + entry = NULL; + return; + } + +#if 0 + int64_t mediaTimeUs; + CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + LOGI("rendering video at media time %.2f secs", mediaTimeUs / 1E6); +#endif + + entry->mNotifyConsumed->setInt32("render", true); + entry->mNotifyConsumed->post(); + mVideoQueue.erase(mVideoQueue.begin()); + entry = NULL; +} + +void NuPlayer::Renderer::notifyEOS(bool audio) { + sp notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->setInt32("audio", static_cast(audio)); + notify->post(); +} + +void NuPlayer::Renderer::onQueueBuffer(const sp &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (dropBufferWhileFlushing(audio, msg)) { + return; + } + + sp obj; + CHECK(msg->findObject("buffer", &obj)); + sp buffer = static_cast(obj.get()); + + sp notifyConsumed; + CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed)); + + QueueEntry entry; + entry.mBuffer = buffer; + entry.mNotifyConsumed = notifyConsumed; + entry.mOffset = 0; + entry.mFinalResult = OK; + + if (audio) { + mAudioQueue.push_back(entry); + postDrainAudioQueue(); + } else { + mVideoQueue.push_back(entry); + postDrainVideoQueue(); + } + + if (mSyncQueues && !mAudioQueue.empty() && !mVideoQueue.empty()) { + int64_t firstAudioTimeUs; + int64_t firstVideoTimeUs; + CHECK((*mAudioQueue.begin()).mBuffer->meta() + ->findInt64("timeUs", &firstAudioTimeUs)); + CHECK((*mVideoQueue.begin()).mBuffer->meta() + ->findInt64("timeUs", &firstVideoTimeUs)); + + int64_t diff = firstVideoTimeUs - firstAudioTimeUs; + + LOGV("queueDiff = %.2f secs", diff / 1E6); + + if (diff > 100000ll) { + // Audio data starts More than 0.1 secs before video. + // Drop some audio. + + (*mAudioQueue.begin()).mNotifyConsumed->post(); + mAudioQueue.erase(mAudioQueue.begin()); + return; + } + + syncQueuesDone(); + } +} + +void NuPlayer::Renderer::syncQueuesDone() { + if (!mSyncQueues) { + return; + } + + mSyncQueues = false; + + if (!mAudioQueue.empty()) { + postDrainAudioQueue(); + } + + if (!mVideoQueue.empty()) { + postDrainVideoQueue(); + } +} + +void NuPlayer::Renderer::onQueueEOS(const sp &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (dropBufferWhileFlushing(audio, msg)) { + return; + } + + int32_t finalResult; + CHECK(msg->findInt32("finalResult", &finalResult)); + + QueueEntry entry; + entry.mOffset = 0; + entry.mFinalResult = finalResult; + + if (audio) { + mAudioQueue.push_back(entry); + postDrainAudioQueue(); + } else { + mVideoQueue.push_back(entry); + postDrainVideoQueue(); + } +} + +void NuPlayer::Renderer::onFlush(const sp &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + // If we're currently syncing the queues, i.e. dropping audio while + // aligning the first audio/video buffer times and only one of the + // two queues has data, we may starve that queue by not requesting + // more buffers from the decoder. If the other source then encounters + // a discontinuity that leads to flushing, we'll never find the + // corresponding discontinuity on the other queue. + // Therefore we'll stop syncing the queues if at least one of them + // is flushed. + syncQueuesDone(); + + if (audio) { + flushQueue(&mAudioQueue); + + Mutex::Autolock autoLock(mFlushLock); + mFlushingAudio = false; + + mDrainAudioQueuePending = false; + ++mAudioQueueGeneration; + } else { + flushQueue(&mVideoQueue); + + Mutex::Autolock autoLock(mFlushLock); + mFlushingVideo = false; + + mDrainVideoQueuePending = false; + ++mVideoQueueGeneration; + } + + notifyFlushComplete(audio); +} + +void NuPlayer::Renderer::flushQueue(List *queue) { + while (!queue->empty()) { + QueueEntry *entry = &*queue->begin(); + + if (entry->mBuffer != NULL) { + entry->mNotifyConsumed->post(); + } + + queue->erase(queue->begin()); + entry = NULL; + } +} + +void NuPlayer::Renderer::notifyFlushComplete(bool audio) { + sp notify = mNotify->dup(); + notify->setInt32("what", kWhatFlushComplete); + notify->setInt32("audio", static_cast(audio)); + notify->post(); +} + +bool NuPlayer::Renderer::dropBufferWhileFlushing( + bool audio, const sp &msg) { + bool flushing = false; + + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + flushing = mFlushingAudio; + } else { + flushing = mFlushingVideo; + } + } + + if (!flushing) { + return false; + } + + sp notifyConsumed; + if (msg->findMessage("notifyConsumed", ¬ifyConsumed)) { + notifyConsumed->post(); + } + + return true; +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h new file mode 100644 index 0000000..834ddc5 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NUPLAYER_RENDERER_H_ + +#define NUPLAYER_RENDERER_H_ + +#include "NuPlayer.h" + +namespace android { + +struct NuPlayer::Renderer : public AHandler { + Renderer(const sp &sink, + const sp ¬ify); + + void queueBuffer( + bool audio, + const sp &buffer, + const sp ¬ifyConsumed); + + void queueEOS(bool audio, status_t finalResult); + + void flush(bool audio); + + void signalTimeDiscontinuity(); + + enum { + kWhatEOS, + kWhatFlushComplete, + }; + +protected: + virtual ~Renderer(); + + virtual void onMessageReceived(const sp &msg); + +private: + enum { + kWhatDrainAudioQueue, + kWhatDrainVideoQueue, + kWhatQueueBuffer, + kWhatQueueEOS, + kWhatFlush, + }; + + struct QueueEntry { + sp mBuffer; + sp mNotifyConsumed; + size_t mOffset; + status_t mFinalResult; + }; + + sp mAudioSink; + sp mNotify; + List mAudioQueue; + List mVideoQueue; + uint32_t mNumFramesWritten; + + bool mDrainAudioQueuePending; + bool mDrainVideoQueuePending; + int32_t mAudioQueueGeneration; + int32_t mVideoQueueGeneration; + + int64_t mAnchorTimeMediaUs; + int64_t mAnchorTimeRealUs; + + Mutex mFlushLock; // protects the following 2 member vars. + bool mFlushingAudio; + bool mFlushingVideo; + + bool mSyncQueues; + + void onDrainAudioQueue(); + void postDrainAudioQueue(); + + void onDrainVideoQueue(); + void postDrainVideoQueue(); + + void onQueueBuffer(const sp &msg); + void onQueueEOS(const sp &msg); + void onFlush(const sp &msg); + + void notifyEOS(bool audio); + void notifyFlushComplete(bool audio); + + void flushQueue(List *queue); + bool dropBufferWhileFlushing(bool audio, const sp &msg); + void syncQueuesDone(); + + DISALLOW_EVIL_CONSTRUCTORS(Renderer); +}; + +} // namespace android + +#endif // NUPLAYER_RENDERER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp new file mode 100644 index 0000000..92642a8 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayerStreamListener" +#include + +#include "NuPlayerStreamListener.h" + +#include +#include + +namespace android { + +NuPlayer::NuPlayerStreamListener::NuPlayerStreamListener( + const sp &source, + ALooper::handler_id id) + : mSource(source), + mTargetID(id), + mEOS(false), + mSendDataNotification(true) { + mSource->setListener(this); + + mMemoryDealer = new MemoryDealer(kNumBuffers * kBufferSize); + for (size_t i = 0; i < kNumBuffers; ++i) { + sp mem = mMemoryDealer->allocate(kBufferSize); + CHECK(mem != NULL); + + mBuffers.push(mem); + } + mSource->setBuffers(mBuffers); +} + +void NuPlayer::NuPlayerStreamListener::start() { + for (size_t i = 0; i < kNumBuffers; ++i) { + mSource->onBufferAvailable(i); + } +} + +void NuPlayer::NuPlayerStreamListener::queueBuffer(size_t index, size_t size) { + QueueEntry entry; + entry.mIsCommand = false; + entry.mIndex = index; + entry.mSize = size; + entry.mOffset = 0; + + Mutex::Autolock autoLock(mLock); + mQueue.push_back(entry); + + if (mSendDataNotification) { + mSendDataNotification = false; + (new AMessage(kWhatMoreDataQueued, mTargetID))->post(); + } +} + +void NuPlayer::NuPlayerStreamListener::issueCommand( + Command cmd, bool synchronous, const sp &extra) { + CHECK(!synchronous); + + QueueEntry entry; + entry.mIsCommand = true; + entry.mCommand = cmd; + entry.mExtra = extra; + + Mutex::Autolock autoLock(mLock); + mQueue.push_back(entry); + + if (mSendDataNotification) { + mSendDataNotification = false; + (new AMessage(kWhatMoreDataQueued, mTargetID))->post(); + } +} + +ssize_t NuPlayer::NuPlayerStreamListener::read(void *data, size_t size) { + CHECK_GT(size, 0u); + + Mutex::Autolock autoLock(mLock); + + if (mEOS) { + return 0; + } + + if (mQueue.empty()) { + mSendDataNotification = true; + + return -EWOULDBLOCK; + } + + QueueEntry *entry = &*mQueue.begin(); + + if (entry->mIsCommand) { + switch (entry->mCommand) { + case EOS: + { + mQueue.erase(mQueue.begin()); + entry = NULL; + + mEOS = true; + return 0; + } + + case DISCONTINUITY: + { + mQueue.erase(mQueue.begin()); + entry = NULL; + + return INFO_DISCONTINUITY; + } + + default: + TRESPASS(); + break; + } + } + + size_t copy = entry->mSize; + if (copy > size) { + copy = size; + } + + memcpy(data, + (const uint8_t *)mBuffers.editItemAt(entry->mIndex)->pointer() + + entry->mOffset, + copy); + + entry->mOffset += copy; + entry->mSize -= copy; + + if (entry->mSize == 0) { + mSource->onBufferAvailable(entry->mIndex); + mQueue.erase(mQueue.begin()); + entry = NULL; + } + + return copy; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h new file mode 100644 index 0000000..f88e945 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NUPLAYER_STREAM_LISTENER_H_ + +#define NUPLAYER_STREAM_LISTENER_H_ + +#include "NuPlayer.h" + +#include + +namespace android { + +struct MemoryDealer; + +struct NuPlayer::NuPlayerStreamListener : public BnStreamListener { + NuPlayerStreamListener( + const sp &source, + ALooper::handler_id targetID); + + virtual void queueBuffer(size_t index, size_t size); + + virtual void issueCommand( + Command cmd, bool synchronous, const sp &extra); + + void start(); + ssize_t read(void *data, size_t size); + +private: + enum { + kNumBuffers = 16, + kBufferSize = 188 * 20 + }; + + struct QueueEntry { + bool mIsCommand; + + size_t mIndex; + size_t mSize; + size_t mOffset; + + Command mCommand; + sp mExtra; + }; + + Mutex mLock; + + sp mSource; + ALooper::handler_id mTargetID; + sp mMemoryDealer; + Vector > mBuffers; + List mQueue; + bool mEOS; + bool mSendDataNotification; + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayerStreamListener); +}; + +} // namespace android + +#endif // NUPLAYER_STREAM_LISTENER_H_ -- cgit v1.1