diff options
author | Andreas Huber <andih@google.com> | 2010-12-15 15:17:42 -0800 |
---|---|---|
committer | Andreas Huber <andih@google.com> | 2010-12-15 15:18:26 -0800 |
commit | f933441648ef6a71dee783d733aac17b9508b452 (patch) | |
tree | 240f8068edb362cbea579659a963bbb029a2bac0 | |
parent | 60c5b57edd3c8f4bdf6b38cf5b8a193ba770bb72 (diff) | |
download | frameworks_av-f933441648ef6a71dee783d733aac17b9508b452.zip frameworks_av-f933441648ef6a71dee783d733aac17b9508b452.tar.gz frameworks_av-f933441648ef6a71dee783d733aac17b9508b452.tar.bz2 |
Initial support for a true streaming player for mpeg2 transport streams.
Change-Id: I153eec439d260a5524b21270e16d36940ec3161a
34 files changed, 5713 insertions, 60 deletions
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index f8650eb..178032d 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -122,3 +122,29 @@ LOCAL_MODULE_TAGS := debug LOCAL_MODULE:= stream include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + sf2.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder libstagefright_foundation \ + libmedia libsurfaceflinger_client libcutils libui + +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + frameworks/base/media/libstagefright \ + $(TOP)/frameworks/base/include/media/stagefright/openmax + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE_TAGS := debug + +LOCAL_MODULE:= sf2 + +include $(BUILD_EXECUTABLE) + + diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp new file mode 100644 index 0000000..1dc08ea --- /dev/null +++ b/cmds/stagefright/sf2.cpp @@ -0,0 +1,564 @@ +#include <binder/ProcessState.h> + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <media/stagefright/ACodec.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include "include/ESDS.h" + +using namespace android; + +struct Controller : public AHandler { + Controller(const char *uri, bool decodeAudio, const sp<Surface> &surface) + : mURI(uri), + mDecodeAudio(decodeAudio), + mSurface(surface), + mCodec(new ACodec) { + CHECK(!mDecodeAudio || mSurface == NULL); + } + + void startAsync() { + (new AMessage(kWhatStart, id()))->post(); + } + +protected: + virtual ~Controller() { + } + + virtual void onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { +#if 1 + mDecodeLooper = looper(); +#else + mDecodeLooper = new ALooper; + mDecodeLooper->setName("sf2 decode looper"); + mDecodeLooper->start(); +#endif + + sp<DataSource> dataSource = + DataSource::CreateFromURI(mURI.c_str()); + + sp<MediaExtractor> extractor = + MediaExtractor::Create(dataSource); + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp(mDecodeAudio ? "audio/" : "video/", + mime, 6)) { + mSource = extractor->getTrack(i); + break; + } + } + CHECK(mSource != NULL); + + CHECK_EQ(mSource->start(), (status_t)OK); + + mDecodeLooper->registerHandler(mCodec); + + mCodec->setNotificationMessage( + new AMessage(kWhatCodecNotify, id())); + + sp<AMessage> format = makeFormat(mSource->getFormat()); + + if (mSurface != NULL) { + format->setObject("surface", mSurface); + } + + mCodec->initiateSetup(format); + + mCSDIndex = 0; + mStartTimeUs = ALooper::GetNowUs(); + mNumOutputBuffersReceived = 0; + mTotalBytesReceived = 0; + mLeftOverBuffer = NULL; + mFinalResult = OK; + mSeekState = SEEK_NONE; + + // (new AMessage(kWhatSeek, id()))->post(5000000ll); + break; + } + + case kWhatSeek: + { + printf("+"); + fflush(stdout); + + CHECK(mSeekState == SEEK_NONE + || mSeekState == SEEK_FLUSH_COMPLETED); + + if (mLeftOverBuffer != NULL) { + mLeftOverBuffer->release(); + mLeftOverBuffer = NULL; + } + + mSeekState = SEEK_FLUSHING; + mSeekTimeUs = 30000000ll; + + mCodec->signalFlush(); + break; + } + + case kWhatStop: + { + if (mLeftOverBuffer != NULL) { + mLeftOverBuffer->release(); + mLeftOverBuffer = NULL; + } + + CHECK_EQ(mSource->stop(), (status_t)OK); + mSource.clear(); + + mCodec->initiateShutdown(); + break; + } + + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + onFillThisBuffer(msg); + } else if (what == ACodec::kWhatDrainThisBuffer) { + if ((mNumOutputBuffersReceived++ % 16) == 0) { + printf("."); + fflush(stdout); + } + + onDrainThisBuffer(msg); + } else if (what == ACodec::kWhatEOS) { + printf("$\n"); + + int64_t delayUs = ALooper::GetNowUs() - mStartTimeUs; + + if (mDecodeAudio) { + printf("%lld bytes received. %.2f KB/sec\n", + mTotalBytesReceived, + mTotalBytesReceived * 1E6 / 1024 / delayUs); + } else { + printf("%d frames decoded, %.2f fps. %lld bytes " + "received. %.2f KB/sec\n", + mNumOutputBuffersReceived, + mNumOutputBuffersReceived * 1E6 / delayUs, + mTotalBytesReceived, + mTotalBytesReceived * 1E6 / 1024 / delayUs); + } + + (new AMessage(kWhatStop, id()))->post(); + } else if (what == ACodec::kWhatFlushCompleted) { + mSeekState = SEEK_FLUSH_COMPLETED; + mCodec->signalResume(); + + (new AMessage(kWhatSeek, id()))->post(5000000ll); + } else { + CHECK_EQ(what, (int32_t)ACodec::kWhatShutdownCompleted); + + mDecodeLooper->unregisterHandler(mCodec->id()); + + if (mDecodeLooper != looper()) { + mDecodeLooper->stop(); + } + + looper()->stop(); + } + break; + } + + default: + TRESPASS(); + break; + } + } + +private: + enum { + kWhatStart = 'strt', + kWhatStop = 'stop', + kWhatCodecNotify = 'noti', + kWhatSeek = 'seek', + }; + + sp<ALooper> mDecodeLooper; + + AString mURI; + bool mDecodeAudio; + sp<Surface> mSurface; + sp<ACodec> mCodec; + sp<MediaSource> mSource; + + Vector<sp<ABuffer> > mCSD; + size_t mCSDIndex; + + MediaBuffer *mLeftOverBuffer; + status_t mFinalResult; + + int64_t mStartTimeUs; + int32_t mNumOutputBuffersReceived; + int64_t mTotalBytesReceived; + + enum SeekState { + SEEK_NONE, + SEEK_FLUSHING, + SEEK_FLUSH_COMPLETED, + }; + SeekState mSeekState; + int64_t mSeekTimeUs; + + sp<AMessage> makeFormat(const sp<MetaData> &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> 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<ABuffer> 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)) { + 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<ABuffer> 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); + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + return msg; + } + + void onFillThisBuffer(const sp<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + if (mSeekState == SEEK_FLUSHING) { + reply->post(); + return; + } + + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + sp<ABuffer> outBuffer = static_cast<ABuffer *>(obj.get()); + + if (mCSDIndex < mCSD.size()) { + outBuffer = mCSD.editItemAt(mCSDIndex++); + outBuffer->meta()->setInt64("timeUs", 0); + } else { + size_t sizeLeft = outBuffer->capacity(); + outBuffer->setRange(0, 0); + + int32_t n = 0; + + for (;;) { + MediaBuffer *inBuffer; + + if (mLeftOverBuffer != NULL) { + inBuffer = mLeftOverBuffer; + mLeftOverBuffer = NULL; + } else if (mFinalResult != OK) { + break; + } else { + MediaSource::ReadOptions options; + if (mSeekState == SEEK_FLUSH_COMPLETED) { + options.setSeekTo(mSeekTimeUs); + mSeekState = SEEK_NONE; + } + status_t err = mSource->read(&inBuffer, &options); + + if (err != OK) { + mFinalResult = err; + break; + } + } + + if (inBuffer->range_length() > sizeLeft) { + if (outBuffer->size() == 0) { + LOGE("Unable to fit even a single input buffer of size %d.", + inBuffer->range_length()); + } + CHECK_GT(outBuffer->size(), 0u); + + mLeftOverBuffer = inBuffer; + break; + } + + ++n; + + if (outBuffer->size() == 0) { + int64_t timeUs; + CHECK(inBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); + + outBuffer->meta()->setInt64("timeUs", timeUs); + } + + memcpy(outBuffer->data() + outBuffer->size(), + (const uint8_t *)inBuffer->data() + + inBuffer->range_offset(), + inBuffer->range_length()); + + outBuffer->setRange( + 0, outBuffer->size() + inBuffer->range_length()); + + sizeLeft -= inBuffer->range_length(); + + inBuffer->release(); + inBuffer = NULL; + + // break; // Don't coalesce + } + + LOGV("coalesced %d input buffers", n); + + if (outBuffer->size() == 0) { + CHECK_NE(mFinalResult, (status_t)OK); + + reply->setInt32("err", mFinalResult); + reply->post(); + return; + } + } + + reply->setObject("buffer", outBuffer); + reply->post(); + } + + void onDrainThisBuffer(const sp<AMessage> &msg) { + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + mTotalBytesReceived += buffer->size(); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + reply->post(); + } + + DISALLOW_EVIL_CONSTRUCTORS(Controller); +}; + +static void usage(const char *me) { + fprintf(stderr, "usage: %s\n", me); + fprintf(stderr, " -h(elp)\n"); + fprintf(stderr, " -a(udio)\n"); + + fprintf(stderr, + " -s(surface) Allocate output buffers on a surface.\n"); +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + bool decodeAudio = false; + bool useSurface = false; + + int res; + while ((res = getopt(argc, argv, "has")) >= 0) { + switch (res) { + case 'a': + decodeAudio = true; + break; + + case 's': + useSurface = true; + break; + + case '?': + case 'h': + default: + { + usage(argv[0]); + return 1; + } + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(argv[-optind]); + return 1; + } + + DataSource::RegisterDefaultSniffers(); + + sp<ALooper> looper = new ALooper; + looper->setName("sf2"); + + sp<SurfaceComposerClient> composerClient; + sp<SurfaceControl> control; + sp<Surface> surface; + + if (!decodeAudio && useSurface) { + composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + control = composerClient->createSurface( + getpid(), + String8("A Surface"), + 0, + 1280, + 800, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + CHECK_EQ(composerClient->openTransaction(), (status_t)OK); + CHECK_EQ(control->setLayer(30000), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + CHECK_EQ(composerClient->closeTransaction(), (status_t)OK); + + surface = control->getSurface(); + CHECK(surface != NULL); + } + + sp<Controller> controller = new Controller(argv[0], decodeAudio, surface); + looper->registerHandler(controller); + + controller->startAsync(); + + CHECK_EQ(looper->start(true /* runOnCallingThread */), (status_t)OK); + + looper->unregisterHandler(controller->id()); + + if (!decodeAudio && useSurface) { + composerClient->dispose(); + } + + return 0; +} + diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp index a3d7a6e..ccae92e 100644 --- a/cmds/stagefright/stream.cpp +++ b/cmds/stagefright/stream.cpp @@ -28,6 +28,8 @@ protected: private: int mFd; + off64_t mFileSize; + int64_t mNextSeekTimeUs; sp<IStreamListener> mListener; Vector<sp<IMemory> > mBuffers; @@ -36,8 +38,13 @@ private: }; MyStreamSource::MyStreamSource(int fd) - : mFd(fd) { + : mFd(fd), + mFileSize(0), + mNextSeekTimeUs(ALooper::GetNowUs() + 5000000ll) { CHECK_GE(fd, 0); + + mFileSize = lseek64(fd, 0, SEEK_END); + lseek64(fd, 0, SEEK_SET); } MyStreamSource::~MyStreamSource() { @@ -53,6 +60,20 @@ void MyStreamSource::setBuffers(const Vector<sp<IMemory> > &buffers) { void MyStreamSource::onBufferAvailable(size_t index) { CHECK_LT(index, mBuffers.size()); + + if (mNextSeekTimeUs >= 0 && mNextSeekTimeUs <= ALooper::GetNowUs()) { + off64_t offset = (off64_t)(((float)rand() / RAND_MAX) * mFileSize * 0.8); + offset = (offset / 188) * 188; + + lseek(mFd, offset, SEEK_SET); + + mListener->issueCommand( + IStreamListener::DISCONTINUITY, false /* synchronous */); + + mNextSeekTimeUs = -1; + mNextSeekTimeUs = ALooper::GetNowUs() + 5000000ll; + } + sp<IMemory> mem = mBuffers.itemAt(index); ssize_t n = read(mFd, mem->pointer(), mem->size()); diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index e7f1d6d..c0963a6 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -40,7 +40,8 @@ template<typename T> class SortedVector; enum player_type { PV_PLAYER = 1, SONIVOX_PLAYER = 2, - STAGEFRIGHT_PLAYER = 4, + STAGEFRIGHT_PLAYER = 3, + NU_PLAYER = 4, // Test players are available only in the 'test' and 'eng' builds. // The shared library with the test player is passed passed as an // argument to the 'test:' url in the setDataSource call. diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h new file mode 100644 index 0000000..940470d --- /dev/null +++ b/include/media/stagefright/ACodec.h @@ -0,0 +1,152 @@ +#ifndef A_CODEC_H_ + +#define A_CODEC_H_ + +#include <stdint.h> +#include <android/native_window.h> +#include <media/IOMX.h> +#include <media/stagefright/foundation/AHierarchicalStateMachine.h> + +namespace android { + +struct ABuffer; +struct MemoryDealer; + +struct ACodec : public AHierarchicalStateMachine { + enum { + kWhatFillThisBuffer = 'fill', + kWhatDrainThisBuffer = 'drai', + kWhatEOS = 'eos ', + kWhatShutdownCompleted = 'scom', + kWhatFlushCompleted = 'fcom', + }; + + ACodec(); + + void setNotificationMessage(const sp<AMessage> &msg); + void initiateSetup(const sp<AMessage> &msg); + void signalFlush(); + void signalResume(); + void initiateShutdown(); + +protected: + virtual ~ACodec(); + +private: + struct BaseState; + struct UninitializedState; + struct LoadedToIdleState; + struct IdleToExecutingState; + struct ExecutingState; + struct OutputPortSettingsChangedState; + struct ExecutingToIdleState; + struct IdleToLoadedState; + struct ErrorState; + struct FlushingState; + + enum { + kWhatSetup = 'setu', + kWhatOMXMessage = 'omx ', + kWhatInputBufferFilled = 'inpF', + kWhatOutputBufferDrained = 'outD', + kWhatShutdown = 'shut', + kWhatFlush = 'flus', + kWhatResume = 'resm', + kWhatDrainDeferredMessages = 'drai', + }; + + enum { + kPortIndexInput = 0, + kPortIndexOutput = 1 + }; + + struct BufferInfo { + enum Status { + OWNED_BY_US, + OWNED_BY_COMPONENT, + OWNED_BY_UPSTREAM, + OWNED_BY_DOWNSTREAM, + OWNED_BY_NATIVE_WINDOW, + }; + + IOMX::buffer_id mBufferID; + Status mStatus; + + sp<ABuffer> mData; + sp<GraphicBuffer> mGraphicBuffer; + }; + + sp<AMessage> mNotify; + + sp<UninitializedState> mUninitializedState; + sp<LoadedToIdleState> mLoadedToIdleState; + sp<IdleToExecutingState> mIdleToExecutingState; + sp<ExecutingState> mExecutingState; + sp<OutputPortSettingsChangedState> mOutputPortSettingsChangedState; + sp<ExecutingToIdleState> mExecutingToIdleState; + sp<IdleToLoadedState> mIdleToLoadedState; + sp<ErrorState> mErrorState; + sp<FlushingState> mFlushingState; + + AString mComponentName; + sp<IOMX> mOMX; + IOMX::node_id mNode; + sp<MemoryDealer> mDealer[2]; + + sp<ANativeWindow> mNativeWindow; + + Vector<BufferInfo> mBuffers[2]; + bool mPortEOS[2]; + + List<sp<AMessage> > mDeferredQueue; + + status_t allocateBuffersOnPort(OMX_U32 portIndex); + status_t freeBuffersOnPort(OMX_U32 portIndex); + status_t freeBuffer(OMX_U32 portIndex, size_t i); + + status_t allocateOutputBuffersFromNativeWindow(); + status_t cancelBufferToNativeWindow(BufferInfo *info); + status_t freeOutputBuffersOwnedByNativeWindow(); + BufferInfo *dequeueBufferFromNativeWindow(); + + BufferInfo *findBufferByID( + uint32_t portIndex, IOMX::buffer_id bufferID, + ssize_t *index = NULL); + + void setComponentRole(bool isEncoder, const char *mime); + void configureCodec(const char *mime, const sp<AMessage> &msg); + + status_t setVideoPortFormatType( + OMX_U32 portIndex, + OMX_VIDEO_CODINGTYPE compressionFormat, + OMX_COLOR_FORMATTYPE colorFormat); + + status_t setSupportedOutputFormat(); + + status_t setupVideoDecoder( + const char *mime, int32_t width, int32_t height); + + status_t setVideoFormatOnPort( + OMX_U32 portIndex, + int32_t width, int32_t height, + OMX_VIDEO_CODINGTYPE compressionFormat); + + status_t setupAACDecoder(int32_t numChannels, int32_t sampleRate); + status_t setMinBufferSize(OMX_U32 portIndex, size_t size); + + status_t initNativeWindow(); + + // Returns true iff all buffers on the given port have status OWNED_BY_US. + bool allYourBuffersAreBelongToUs(OMX_U32 portIndex); + + bool allYourBuffersAreBelongToUs(); + + void deferMessage(const sp<AMessage> &msg); + void processDeferredMessages(); + + DISALLOW_EVIL_CONSTRUCTORS(ACodec); +}; + +} // namespace android + +#endif // A_CODEC_H_ diff --git a/include/media/stagefright/foundation/ADebug.h b/include/media/stagefright/foundation/ADebug.h index 69021d8..eb5e494 100644 --- a/include/media/stagefright/foundation/ADebug.h +++ b/include/media/stagefright/foundation/ADebug.h @@ -32,6 +32,7 @@ namespace android { #define CHECK(condition) \ LOG_ALWAYS_FATAL_IF( \ !(condition), \ + "%s", \ __FILE__ ":" LITERAL_TO_STRING(__LINE__) \ " CHECK(" #condition ") failed.") @@ -58,10 +59,12 @@ MAKE_COMPARATOR(GT,>) do { \ AString ___res = Compare_##suffix(x, y); \ if (!___res.empty()) { \ - LOG_ALWAYS_FATAL( \ - __FILE__ ":" LITERAL_TO_STRING(__LINE__) \ - " CHECK_" #suffix "( " #x "," #y ") failed: %s", \ - ___res.c_str()); \ + AString ___full = \ + __FILE__ ":" LITERAL_TO_STRING(__LINE__) \ + " CHECK_" #suffix "( " #x "," #y ") failed: "; \ + ___full.append(___res); \ + \ + LOG_ALWAYS_FATAL("%s", ___full.c_str()); \ } \ } while (false) diff --git a/include/media/stagefright/foundation/AHierarchicalStateMachine.h b/include/media/stagefright/foundation/AHierarchicalStateMachine.h new file mode 100644 index 0000000..b5786fb --- /dev/null +++ b/include/media/stagefright/foundation/AHierarchicalStateMachine.h @@ -0,0 +1,49 @@ +#ifndef A_HIERARCHICAL_STATE_MACHINE_H_ + +#define A_HIERARCHICAL_STATE_MACHINE_H_ + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct AState : public RefBase { + AState(const sp<AState> &parentState = NULL); + + sp<AState> parentState(); + +protected: + virtual ~AState(); + + virtual void stateEntered(); + virtual void stateExited(); + + virtual bool onMessageReceived(const sp<AMessage> &msg) = 0; + +private: + friend struct AHierarchicalStateMachine; + + sp<AState> mParentState; + + DISALLOW_EVIL_CONSTRUCTORS(AState); +}; + +struct AHierarchicalStateMachine : public AHandler { + AHierarchicalStateMachine(); + +protected: + virtual ~AHierarchicalStateMachine(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + + // Only to be called in response to a message. + void changeState(const sp<AState> &state); + +private: + sp<AState> mState; + + DISALLOW_EVIL_CONSTRUCTORS(AHierarchicalStateMachine); +}; + +} // namespace android + +#endif // A_HIERARCHICAL_STATE_MACHINE_H_ diff --git a/include/media/stagefright/foundation/AMessage.h b/include/media/stagefright/foundation/AMessage.h index 2fbdddc..941f6b9 100644 --- a/include/media/stagefright/foundation/AMessage.h +++ b/include/media/stagefright/foundation/AMessage.h @@ -40,6 +40,8 @@ struct AMessage : public RefBase { void setTarget(ALooper::handler_id target); ALooper::handler_id target() const; + void clear(); + void setInt32(const char *name, int32_t value); void setInt64(const char *name, int64_t value); void setSize(const char *name, size_t value); @@ -106,7 +108,6 @@ private: Item mItems[kMaxNumItems]; size_t mNumItems; - void clear(); Item *allocateItem(const char *name); void freeItem(Item *item); const Item *findItem(const char *name, Type type) const; 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 <OMX.h> @@ -759,6 +760,10 @@ static sp<MediaPlayerBase> 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<IStreamSource> &source) { // create the right type of player - sp<MediaPlayerBase> p = createPlayer(STAGEFRIGHT_PLAYER); + sp<MediaPlayerBase> 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 <utils/Log.h> + +#include "DecoderWrapper.h" + +#include "AACDecoder.h" + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +struct DecoderWrapper::WrapperSource : public MediaSource { + WrapperSource( + const sp<MetaData> &meta, + const sp<AMessage> ¬ify); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + + void queueBuffer(const sp<ABuffer> &buffer); + void queueEOS(status_t finalResult); + void clear(); + +protected: + virtual ~WrapperSource(); + +private: + Mutex mLock; + Condition mCondition; + + sp<MetaData> mMeta; + sp<AMessage> mNotify; + + List<sp<ABuffer> > mQueue; + status_t mFinalResult; + + DISALLOW_EVIL_CONSTRUCTORS(WrapperSource); +}; + +DecoderWrapper::WrapperSource::WrapperSource( + const sp<MetaData> &meta, const sp<AMessage> ¬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<MetaData> 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<ABuffer> 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<ABuffer> &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<MediaSource> &decoder, + const sp<AMessage> ¬ify); + + void start(); + void readMore(bool flush = false); + +protected: + virtual ~WrapperReader(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatRead + }; + + sp<MediaSource> mDecoder; + sp<AMessage> mNotify; + bool mEOS; + + DISALLOW_EVIL_CONSTRUCTORS(WrapperReader); +}; + +DecoderWrapper::WrapperReader::WrapperReader( + const sp<MediaSource> &decoder, const sp<AMessage> ¬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<AMessage> msg = new AMessage(kWhatRead, id()); + msg->setInt32("flush", static_cast<int32_t>(flush)); + msg->post(); +} + +void DecoderWrapper::WrapperReader::onMessageReceived( + const sp<AMessage> &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<AMessage> notify = mNotify->dup(); + + sp<AMessage> realNotify; + CHECK(notify->findMessage("real-notify", &realNotify)); + + if (err == OK) { + realNotify->setInt32("what", ACodec::kWhatDrainThisBuffer); + + sp<ABuffer> 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<AMessage> &msg) { + mNotify = msg; +} + +void DecoderWrapper::initiateSetup(const sp<AMessage> &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<AMessage> &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<RefBase> obj; + if (!msg->findObject("buffer", &obj)) { + int32_t err = OK; + CHECK(msg->findInt32("err", &err)); + + mSource->queueEOS(err); + break; + } + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + + mSource->queueBuffer(buffer); + break; + } + + case kWhatFillBufferDone: + { + CHECK_GT(mNumPendingDecodes, 0); + --mNumPendingDecodes; + + if (mFlushing) { + completeFlushIfPossible(); + break; + } + + sp<AMessage> notify; + CHECK(msg->findMessage("real-notify", ¬ify)); + + sp<AMessage> 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<AMessage> &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<RefBase> obj; + CHECK(msg->findObject("esds", &obj)); + sp<ABuffer> esds = static_cast<ABuffer *>(obj.get()); + + sp<MetaData> 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<MediaSource> decoder = new AACDecoder(mSource); + + mReaderLooper = new ALooper; + mReaderLooper->setName("DecoderWrapper looper"); + + mReaderLooper->start( + false, /* runOnCallingThread */ + false, /* canCallJava */ + PRIORITY_AUDIO); + + sp<AMessage> 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<AMessage> notify = mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFillThisBuffer); + sp<AMessage> 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<AMessage> 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 <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct MediaSource; + +struct DecoderWrapper : public AHandler { + DecoderWrapper(); + + void setNotificationMessage(const sp<AMessage> &msg); + void initiateSetup(const sp<AMessage> &msg); + void initiateShutdown(); + void signalFlush(); + void signalResume(); + +protected: + virtual ~DecoderWrapper(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + struct WrapperSource; + struct WrapperReader; + + enum { + kWhatSetup, + kWhatInputBufferFilled, + kWhatOutputBufferDrained, + kWhatShutdown, + kWhatFillBufferDone, + kWhatInputDataRequested, + kWhatFlush, + kWhatResume, + }; + + sp<AMessage> mNotify; + + sp<WrapperSource> mSource; + + sp<ALooper> mReaderLooper; + sp<WrapperReader> mReader; + + int32_t mNumOutstandingInputBuffers; + int32_t mNumOutstandingOutputBuffers; + int32_t mNumPendingDecodes; + bool mFlushing; + + void onSetup(const sp<AMessage> &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 <utils/Log.h> + +#include "NuPlayer.h" +#include "NuPlayerDecoder.h" +#include "NuPlayerRenderer.h" +#include "NuPlayerStreamListener.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <surfaceflinger/Surface.h> + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +NuPlayer::NuPlayer() + : mEOS(false), + mAudioEOS(false), + mVideoEOS(false), + mFlushingAudio(NONE), + mFlushingVideo(NONE) { +} + +NuPlayer::~NuPlayer() { +} + +void NuPlayer::setListener(const wp<MediaPlayerBase> &listener) { + mListener = listener; +} + +void NuPlayer::setDataSource(const sp<IStreamSource> &source) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); + + source->incStrong(this); + msg->setPointer("source", source.get()); // XXX unsafe. + + msg->post(); +} + +void NuPlayer::setVideoSurface(const sp<Surface> &surface) { + sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, id()); + msg->setObject("surface", surface); + msg->post(); +} + +void NuPlayer::setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink) { + sp<AMessage> msg = new AMessage(kWhatSetAudioSink, id()); + msg->setObject("sink", sink); + msg->post(); +} + +void NuPlayer::start() { + (new AMessage(kWhatStart, id()))->post(); +} + +void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatSetDataSource: + { + LOGI("kWhatSetDataSource"); + + CHECK(mSource == NULL); + + void *ptr; + CHECK(msg->findPointer("source", &ptr)); + + mSource = static_cast<IStreamSource *>(ptr); + mSource->decStrong(this); + + mStreamListener = new NuPlayerStreamListener(mSource, id()); + mTSParser = new ATSParser; + break; + } + + case kWhatSetVideoSurface: + { + LOGI("kWhatSetVideoSurface"); + + sp<RefBase> obj; + CHECK(msg->findObject("surface", &obj)); + + mSurface = static_cast<Surface *>(obj.get()); + break; + } + + case kWhatSetAudioSink: + { + LOGI("kWhatSetAudioSink"); + + sp<RefBase> obj; + CHECK(msg->findObject("sink", &obj)); + + mAudioSink = static_cast<MediaPlayerBase::AudioSink *>(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<AMessage> 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<ABuffer> *accessUnit) { + accessUnit->clear(); + + status_t audioErr = -EWOULDBLOCK; + int64_t audioTimeUs; + + sp<AnotherPacketSource> audioSource = + static_cast<AnotherPacketSource *>( + mTSParser->getSource(ATSParser::MPEG2ADTS_AUDIO).get()); + + if (audioSource != NULL) { + audioErr = audioSource->nextBufferTime(&audioTimeUs); + } + + status_t videoErr = -EWOULDBLOCK; + int64_t videoTimeUs; + + sp<AnotherPacketSource> videoSource = + static_cast<AnotherPacketSource *>( + 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<ABuffer> *accessUnit) { + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>(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> *decoder) { + if (*decoder != NULL) { + return OK; + } + + ATSParser::SourceType type = + audio ? ATSParser::MPEG2ADTS_AUDIO : ATSParser::AVC_VIDEO; + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>( + mTSParser->getSource(type).get()); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + sp<AMessage> notify = + new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify, + id()); + + *decoder = new Decoder(notify, audio ? NULL : mSurface); + looper()->registerHandler(*decoder); + + const sp<MetaData> &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<AMessage> &msg) { + sp<AMessage> 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<ABuffer> 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<AMessage> &msg) { + LOGV("renderBuffer %s", audio ? "audio" : "video"); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + + mRenderer->queueBuffer(audio, buffer, reply); +} + +void NuPlayer::notifyListener(int msg, int ext1, int ext2) { + if (mListener == NULL) { + return; + } + + sp<MediaPlayerBase> 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 <media/MediaPlayerInterface.h> +#include <media/stagefright/foundation/AHandler.h> + +#include "ATSParser.h" +#include "AnotherPacketSource.h" + +namespace android { + +struct ACodec; +struct MetaData; + +struct NuPlayer : public AHandler { + NuPlayer(); + + void setListener(const wp<MediaPlayerBase> &listener); + + void setDataSource(const sp<IStreamSource> &source); + void setVideoSurface(const sp<Surface> &surface); + void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink); + void start(); + +protected: + virtual ~NuPlayer(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + struct Renderer; + struct Decoder; + struct NuPlayerStreamListener; + + enum { + kWhatSetDataSource, + kWhatSetVideoSurface, + kWhatSetAudioSink, + kWhatMoreDataQueued, + kWhatStart, + kWhatScanSources, + kWhatVideoNotify, + kWhatAudioNotify, + kWhatRendererNotify, + }; + + wp<MediaPlayerBase> mListener; + sp<IStreamSource> mSource; + sp<Surface> mSurface; + sp<MediaPlayerBase::AudioSink> mAudioSink; + sp<NuPlayerStreamListener> mStreamListener; + sp<ATSParser> mTSParser; + sp<Decoder> mVideoDecoder; + sp<Decoder> mAudioDecoder; + sp<Renderer> 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> *decoder); + + status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg); + void renderBuffer(bool audio, const sp<AMessage> &msg); + + status_t dequeueNextAccessUnit( + ATSParser::SourceType *type, sp<ABuffer> *accessUnit); + + status_t dequeueAccessUnit( + ATSParser::SourceType type, sp<ABuffer> *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 <utils/Log.h> + +#include "NuPlayerDecoder.h" + +#include "DecoderWrapper.h" +#include "ESDS.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> +#include <surfaceflinger/Surface.h> + +namespace android { + +NuPlayer::Decoder::Decoder( + const sp<AMessage> ¬ify, const sp<Surface> &surface) + : mNotify(notify), + mSurface(surface) { +} + +NuPlayer::Decoder::~Decoder() { +} + +void NuPlayer::Decoder::configure(const sp<MetaData> &meta) { + CHECK(mCodec == NULL); + CHECK(mWrapper == NULL); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> notifyMsg = + new AMessage(kWhatCodecNotify, id()); + + sp<AMessage> 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<AMessage> &msg) { + switch (msg->what()) { + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + onFillThisBuffer(msg); + } else { + sp<AMessage> notify = mNotify->dup(); + notify->setMessage("codec-request", msg); + notify->post(); + } + break; + } + + default: + TRESPASS(); + break; + } +} + +sp<AMessage> NuPlayer::Decoder::makeFormat(const sp<MetaData> &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> 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<ABuffer> 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<ABuffer> 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<ABuffer> 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<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + +#if 0 + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + sp<ABuffer> outBuffer = static_cast<ABuffer *>(obj.get()); +#else + sp<ABuffer> outBuffer; +#endif + + if (mCSDIndex < mCSD.size()) { + outBuffer = mCSD.editItemAt(mCSDIndex++); + outBuffer->meta()->setInt64("timeUs", 0); + + reply->setObject("buffer", outBuffer); + reply->post(); + return; + } + + sp<AMessage> 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 <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct DecoderWrapper; + +struct NuPlayer::Decoder : public AHandler { + Decoder(const sp<AMessage> ¬ify, const sp<Surface> &surface = NULL); + + void configure(const sp<MetaData> &meta); + void signalFlush(); + void signalResume(); + +protected: + virtual ~Decoder(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatCodecNotify, + }; + + sp<AMessage> mNotify; + sp<Surface> mSurface; + + sp<ACodec> mCodec; + sp<DecoderWrapper> mWrapper; + + Vector<sp<ABuffer> > mCSD; + size_t mCSDIndex; + + sp<AMessage> makeFormat(const sp<MetaData> &meta); + + void onFillThisBuffer(const sp<AMessage> &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 <utils/Log.h> + +#include "NuPlayerDriver.h" + +#include "NuPlayer.h" + +#include <media/stagefright/foundation/ALooper.h> + +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<String8, String8> *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<IStreamSource> &source) { + mPlayer->setDataSource(source); + + return OK; +} + +status_t NuPlayerDriver::setVideoSurface(const sp<Surface> &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> &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 <media/MediaPlayerInterface.h> + +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +struct ALooper; +struct NuPlayer; + +struct NuPlayerDriver : public MediaPlayerInterface { + NuPlayerDriver(); + + virtual status_t initCheck(); + + virtual status_t setDataSource( + const char *url, const KeyedVector<String8, String8> *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual status_t setDataSource(const sp<IStreamSource> &source); + + virtual status_t setVideoSurface(const sp<Surface> &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> &audioSink); + + virtual status_t getMetadata( + const media::Metadata::Filter& ids, Parcel *records); + +protected: + virtual ~NuPlayerDriver(); + +private: + sp<ALooper> mLooper; + sp<NuPlayer> 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 <utils/Log.h> + +#include "NuPlayerRenderer.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> + +namespace android { + +NuPlayer::Renderer::Renderer( + const sp<MediaPlayerBase::AudioSink> &sink, + const sp<AMessage> ¬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<ABuffer> &buffer, + const sp<AMessage> ¬ifyConsumed) { + sp<AMessage> msg = new AMessage(kWhatQueueBuffer, id()); + msg->setInt32("audio", static_cast<int32_t>(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<AMessage> msg = new AMessage(kWhatQueueEOS, id()); + msg->setInt32("audio", static_cast<int32_t>(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<AMessage> msg = new AMessage(kWhatFlush, id()); + msg->setInt32("audio", static_cast<int32_t>(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<AMessage> &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<AMessage> 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<AMessage> 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<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->setInt32("audio", static_cast<int32_t>(audio)); + notify->post(); +} + +void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (dropBufferWhileFlushing(audio, msg)) { + return; + } + + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + + sp<AMessage> 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<AMessage> &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<AMessage> &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<QueueEntry> *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<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFlushComplete); + notify->setInt32("audio", static_cast<int32_t>(audio)); + notify->post(); +} + +bool NuPlayer::Renderer::dropBufferWhileFlushing( + bool audio, const sp<AMessage> &msg) { + bool flushing = false; + + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + flushing = mFlushingAudio; + } else { + flushing = mFlushingVideo; + } + } + + if (!flushing) { + return false; + } + + sp<AMessage> 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<MediaPlayerBase::AudioSink> &sink, + const sp<AMessage> ¬ify); + + void queueBuffer( + bool audio, + const sp<ABuffer> &buffer, + const sp<AMessage> ¬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<AMessage> &msg); + +private: + enum { + kWhatDrainAudioQueue, + kWhatDrainVideoQueue, + kWhatQueueBuffer, + kWhatQueueEOS, + kWhatFlush, + }; + + struct QueueEntry { + sp<ABuffer> mBuffer; + sp<AMessage> mNotifyConsumed; + size_t mOffset; + status_t mFinalResult; + }; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + sp<AMessage> mNotify; + List<QueueEntry> mAudioQueue; + List<QueueEntry> 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<AMessage> &msg); + void onQueueEOS(const sp<AMessage> &msg); + void onFlush(const sp<AMessage> &msg); + + void notifyEOS(bool audio); + void notifyFlushComplete(bool audio); + + void flushQueue(List<QueueEntry> *queue); + bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &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 <utils/Log.h> + +#include "NuPlayerStreamListener.h" + +#include <binder/MemoryDealer.h> +#include <media/stagefright/foundation/ADebug.h> + +namespace android { + +NuPlayer::NuPlayerStreamListener::NuPlayerStreamListener( + const sp<IStreamSource> &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<IMemory> 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<AMessage> &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 <media/IStreamSource.h> + +namespace android { + +struct MemoryDealer; + +struct NuPlayer::NuPlayerStreamListener : public BnStreamListener { + NuPlayerStreamListener( + const sp<IStreamSource> &source, + ALooper::handler_id targetID); + + virtual void queueBuffer(size_t index, size_t size); + + virtual void issueCommand( + Command cmd, bool synchronous, const sp<AMessage> &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<AMessage> mExtra; + }; + + Mutex mLock; + + sp<IStreamSource> mSource; + ALooper::handler_id mTargetID; + sp<MemoryDealer> mMemoryDealer; + Vector<sp<IMemory> > mBuffers; + List<QueueEntry> mQueue; + bool mEOS; + bool mSendDataNotification; + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayerStreamListener); +}; + +} // namespace android + +#endif // NUPLAYER_STREAM_LISTENER_H_ diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp new file mode 100644 index 0000000..77276ab --- /dev/null +++ b/media/libstagefright/ACodec.cpp @@ -0,0 +1,2097 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "ACodec" + +#include <media/stagefright/ACodec.h> + +#include <binder/MemoryDealer.h> + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/OMXClient.h> + +#include <surfaceflinger/Surface.h> + +#include <OMX_Component.h> + +namespace android { + +template<class T> +static void InitOMXParams(T *params) { + params->nSize = sizeof(T); + params->nVersion.s.nVersionMajor = 1; + params->nVersion.s.nVersionMinor = 0; + params->nVersion.s.nRevision = 0; + params->nVersion.s.nStep = 0; +} + +struct CodecObserver : public BnOMXObserver { + CodecObserver() {} + + void setNotificationMessage(const sp<AMessage> &msg) { + mNotify = msg; + } + + // from IOMXObserver + virtual void onMessage(const omx_message &omx_msg) { + sp<AMessage> msg = mNotify->dup(); + + msg->setInt32("type", omx_msg.type); + msg->setPointer("node", omx_msg.node); + + switch (omx_msg.type) { + case omx_message::EVENT: + { + msg->setInt32("event", omx_msg.u.event_data.event); + msg->setInt32("data1", omx_msg.u.event_data.data1); + msg->setInt32("data2", omx_msg.u.event_data.data2); + break; + } + + case omx_message::EMPTY_BUFFER_DONE: + { + msg->setPointer("buffer", omx_msg.u.buffer_data.buffer); + break; + } + + case omx_message::FILL_BUFFER_DONE: + { + msg->setPointer( + "buffer", omx_msg.u.extended_buffer_data.buffer); + msg->setInt32( + "range_offset", + omx_msg.u.extended_buffer_data.range_offset); + msg->setInt32( + "range_length", + omx_msg.u.extended_buffer_data.range_length); + msg->setInt32( + "flags", + omx_msg.u.extended_buffer_data.flags); + msg->setInt64( + "timestamp", + omx_msg.u.extended_buffer_data.timestamp); + msg->setPointer( + "platform_private", + omx_msg.u.extended_buffer_data.platform_private); + msg->setPointer( + "data_ptr", + omx_msg.u.extended_buffer_data.data_ptr); + break; + } + + default: + TRESPASS(); + break; + } + + msg->post(); + } + +protected: + virtual ~CodecObserver() {} + +private: + sp<AMessage> mNotify; + + DISALLOW_EVIL_CONSTRUCTORS(CodecObserver); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::BaseState : public AState { + BaseState(ACodec *codec, const sp<AState> &parentState = NULL); + +protected: + enum PortMode { + KEEP_BUFFERS, + RESUBMIT_BUFFERS, + FREE_BUFFERS, + }; + + ACodec *mCodec; + + virtual PortMode getPortMode(OMX_U32 portIndex); + + virtual bool onMessageReceived(const sp<AMessage> &msg); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + + virtual void onOutputBufferDrained(const sp<AMessage> &msg); + virtual void onInputBufferFilled(const sp<AMessage> &msg); + + void postFillThisBuffer(BufferInfo *info); + +private: + bool onOMXMessage(const sp<AMessage> &msg); + + bool onOMXEmptyBufferDone(IOMX::buffer_id bufferID); + + bool onOMXFillBufferDone( + IOMX::buffer_id bufferID, + size_t rangeOffset, size_t rangeLength, + OMX_U32 flags, + int64_t timeUs, + void *platformPrivate, + void *dataPtr); + + void getMoreInputDataIfPossible(); + + DISALLOW_EVIL_CONSTRUCTORS(BaseState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::UninitializedState : public ACodec::BaseState { + UninitializedState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + +private: + void onSetup(const sp<AMessage> &msg); + + DISALLOW_EVIL_CONSTRUCTORS(UninitializedState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::LoadedToIdleState : public ACodec::BaseState { + LoadedToIdleState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + virtual void stateEntered(); + +private: + status_t allocateBuffers(); + + DISALLOW_EVIL_CONSTRUCTORS(LoadedToIdleState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::IdleToExecutingState : public ACodec::BaseState { + IdleToExecutingState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + virtual void stateEntered(); + +private: + DISALLOW_EVIL_CONSTRUCTORS(IdleToExecutingState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::ExecutingState : public ACodec::BaseState { + ExecutingState(ACodec *codec); + + void submitOutputBuffers(); + + // Submit output buffers to the decoder, submit input buffers to client + // to fill with data. + void resume(); + +protected: + virtual PortMode getPortMode(OMX_U32 portIndex); + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + +private: + DISALLOW_EVIL_CONSTRUCTORS(ExecutingState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::OutputPortSettingsChangedState : public ACodec::BaseState { + OutputPortSettingsChangedState(ACodec *codec); + +protected: + virtual PortMode getPortMode(OMX_U32 portIndex); + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + +private: + DISALLOW_EVIL_CONSTRUCTORS(OutputPortSettingsChangedState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::ExecutingToIdleState : public ACodec::BaseState { + ExecutingToIdleState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + + virtual void onOutputBufferDrained(const sp<AMessage> &msg); + virtual void onInputBufferFilled(const sp<AMessage> &msg); + +private: + void changeStateIfWeOwnAllBuffers(); + + DISALLOW_EVIL_CONSTRUCTORS(ExecutingToIdleState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::IdleToLoadedState : public ACodec::BaseState { + IdleToLoadedState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + +private: + DISALLOW_EVIL_CONSTRUCTORS(IdleToLoadedState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::ErrorState : public ACodec::BaseState { + ErrorState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + +private: + DISALLOW_EVIL_CONSTRUCTORS(ErrorState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct ACodec::FlushingState : public ACodec::BaseState { + FlushingState(ACodec *codec); + +protected: + virtual bool onMessageReceived(const sp<AMessage> &msg); + virtual void stateEntered(); + + virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + + virtual void onOutputBufferDrained(const sp<AMessage> &msg); + virtual void onInputBufferFilled(const sp<AMessage> &msg); + +private: + bool mFlushComplete[2]; + + void changeStateIfWeOwnAllBuffers(); + + DISALLOW_EVIL_CONSTRUCTORS(FlushingState); +}; + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::ACodec() + : mNode(NULL) { + mUninitializedState = new UninitializedState(this); + mLoadedToIdleState = new LoadedToIdleState(this); + mIdleToExecutingState = new IdleToExecutingState(this); + mExecutingState = new ExecutingState(this); + + mOutputPortSettingsChangedState = + new OutputPortSettingsChangedState(this); + + mExecutingToIdleState = new ExecutingToIdleState(this); + mIdleToLoadedState = new IdleToLoadedState(this); + mErrorState = new ErrorState(this); + mFlushingState = new FlushingState(this); + + mPortEOS[kPortIndexInput] = mPortEOS[kPortIndexOutput] = false; + + changeState(mUninitializedState); +} + +ACodec::~ACodec() { +} + +void ACodec::setNotificationMessage(const sp<AMessage> &msg) { + mNotify = msg; +} + +void ACodec::initiateSetup(const sp<AMessage> &msg) { + msg->setWhat(kWhatSetup); + msg->setTarget(id()); + msg->post(); +} + +void ACodec::signalFlush() { + (new AMessage(kWhatFlush, id()))->post(); +} + +void ACodec::signalResume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void ACodec::initiateShutdown() { + (new AMessage(kWhatShutdown, id()))->post(); +} + +status_t ACodec::allocateBuffersOnPort(OMX_U32 portIndex) { + CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput); + + CHECK(mDealer[portIndex] == NULL); + CHECK(mBuffers[portIndex].isEmpty()); + + if (mNativeWindow != NULL && portIndex == kPortIndexOutput) { + return allocateOutputBuffersFromNativeWindow(); + } + + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = portIndex; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + LOGV("[%s] Allocating %lu buffers of size %lu on %s port", + mComponentName.c_str(), + def.nBufferCountActual, def.nBufferSize, + portIndex == kPortIndexInput ? "input" : "output"); + + size_t totalSize = def.nBufferCountActual * def.nBufferSize; + mDealer[portIndex] = new MemoryDealer(totalSize, "OMXCodec"); + + for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) { + sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize); + CHECK(mem.get() != NULL); + + IOMX::buffer_id buffer; +#if 0 + err = mOMX->allocateBufferWithBackup(mNode, portIndex, mem, &buffer); +#else + err = mOMX->useBuffer(mNode, portIndex, mem, &buffer); +#endif + + if (err != OK) { + return err; + } + + BufferInfo info; + info.mBufferID = buffer; + info.mStatus = BufferInfo::OWNED_BY_US; + info.mData = new ABuffer(mem->pointer(), def.nBufferSize); + mBuffers[portIndex].push(info); + } + + return OK; +} + +status_t ACodec::allocateOutputBuffersFromNativeWindow() { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + err = native_window_set_buffers_geometry( + mNativeWindow.get(), + def.format.video.nFrameWidth, + def.format.video.nFrameHeight, + def.format.video.eColorFormat); + + if (err != 0) { + LOGE("native_window_set_buffers_geometry failed: %s (%d)", + strerror(-err), -err); + return err; + } + + // Increase the buffer count by one to allow for the ANativeWindow to hold + // on to one of the buffers. + def.nBufferCountActual++; + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + // Set up the native window. + // XXX TODO: Get the gralloc usage flags from the OMX plugin! + err = native_window_set_usage( + mNativeWindow.get(), + GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_EXTERNAL_DISP); + + if (err != 0) { + LOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err); + return err; + } + + err = native_window_set_buffer_count( + mNativeWindow.get(), def.nBufferCountActual); + + if (err != 0) { + LOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err), + -err); + return err; + } + + // XXX TODO: Do something so the ANativeWindow knows that we'll need to get + // the same set of buffers. + + LOGV("[%s] Allocating %lu buffers from a native window of size %lu on " + "output port", + mComponentName.c_str(), def.nBufferCountActual, def.nBufferSize); + + // Dequeue buffers and send them to OMX + OMX_U32 i; + for (i = 0; i < def.nBufferCountActual; i++) { + android_native_buffer_t *buf; + err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf); + if (err != 0) { + LOGE("dequeueBuffer failed: %s (%d)", strerror(-err), -err); + break; + } + + sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(buf, false)); + IOMX::buffer_id bufferId; + err = mOMX->useGraphicBuffer(mNode, kPortIndexOutput, graphicBuffer, + &bufferId); + if (err != 0) { + break; + } + + LOGV("[%s] Registered graphic buffer with ID %p (pointer = %p)", + mComponentName.c_str(), + bufferId, graphicBuffer.get()); + + BufferInfo info; + info.mBufferID = bufferId; + info.mStatus = BufferInfo::OWNED_BY_US; + info.mData = new ABuffer(0); + info.mGraphicBuffer = graphicBuffer; + mBuffers[kPortIndexOutput].push(info); + } + + OMX_U32 cancelStart; + OMX_U32 cancelEnd; + + if (err != 0) { + // If an error occurred while dequeuing we need to cancel any buffers + // that were dequeued. + cancelStart = 0; + cancelEnd = i; + } else { + // Return the last two buffers to the native window. + // XXX TODO: The number of buffers the native window owns should + // probably be queried from it when we put the native window in + // fixed buffer pool mode (which needs to be implemented). + // Currently it's hard-coded to 2. + cancelStart = def.nBufferCountActual - 2; + cancelEnd = def.nBufferCountActual; + } + + for (OMX_U32 i = cancelStart; i < cancelEnd; i++) { + BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i); + cancelBufferToNativeWindow(info); + } + + return err; +} + +status_t ACodec::cancelBufferToNativeWindow(BufferInfo *info) { + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US); + + LOGV("[%s] Calling cancelBuffer on buffer %p", + mComponentName.c_str(), info->mBufferID); + + int err = mNativeWindow->cancelBuffer( + mNativeWindow.get(), info->mGraphicBuffer.get()); + + CHECK_EQ(err, 0); + + info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; + + return OK; +} + +ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() { + android_native_buffer_t *buf; + CHECK_EQ(mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf), 0); + + for (size_t i = mBuffers[kPortIndexOutput].size(); i-- > 0;) { + BufferInfo *info = + &mBuffers[kPortIndexOutput].editItemAt(i); + + if (info->mGraphicBuffer->handle == buf->handle) { + CHECK_EQ((int)info->mStatus, + (int)BufferInfo::OWNED_BY_NATIVE_WINDOW); + + info->mStatus = BufferInfo::OWNED_BY_US; + + return info; + } + } + + TRESPASS(); + + return NULL; +} + +status_t ACodec::freeBuffersOnPort(OMX_U32 portIndex) { + for (size_t i = mBuffers[portIndex].size(); i-- > 0;) { + CHECK_EQ((status_t)OK, freeBuffer(portIndex, i)); + } + + mDealer[portIndex].clear(); + + return OK; +} + +status_t ACodec::freeOutputBuffersOwnedByNativeWindow() { + for (size_t i = mBuffers[kPortIndexOutput].size(); i-- > 0;) { + BufferInfo *info = + &mBuffers[kPortIndexOutput].editItemAt(i); + + if (info->mStatus == + BufferInfo::OWNED_BY_NATIVE_WINDOW) { + CHECK_EQ((status_t)OK, freeBuffer(kPortIndexOutput, i)); + } + } + + return OK; +} + +status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) { + BufferInfo *info = &mBuffers[portIndex].editItemAt(i); + + CHECK(info->mStatus == BufferInfo::OWNED_BY_US + || info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW); + + if (portIndex == kPortIndexOutput && mNativeWindow != NULL + && info->mStatus == BufferInfo::OWNED_BY_US) { + CHECK_EQ((status_t)OK, cancelBufferToNativeWindow(info)); + } + + CHECK_EQ(mOMX->freeBuffer( + mNode, portIndex, info->mBufferID), + (status_t)OK); + + mBuffers[portIndex].removeAt(i); + + return OK; +} + +ACodec::BufferInfo *ACodec::findBufferByID( + uint32_t portIndex, IOMX::buffer_id bufferID, + ssize_t *index) { + for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) { + BufferInfo *info = &mBuffers[portIndex].editItemAt(i); + + if (info->mBufferID == bufferID) { + if (index != NULL) { + *index = i; + } + return info; + } + } + + TRESPASS(); + + return NULL; +} + +void ACodec::setComponentRole( + bool isEncoder, const char *mime) { + struct MimeToRole { + const char *mime; + const char *decoderRole; + const char *encoderRole; + }; + + static const MimeToRole kMimeToRole[] = { + { MEDIA_MIMETYPE_AUDIO_MPEG, + "audio_decoder.mp3", "audio_encoder.mp3" }, + { MEDIA_MIMETYPE_AUDIO_AMR_NB, + "audio_decoder.amrnb", "audio_encoder.amrnb" }, + { MEDIA_MIMETYPE_AUDIO_AMR_WB, + "audio_decoder.amrwb", "audio_encoder.amrwb" }, + { MEDIA_MIMETYPE_AUDIO_AAC, + "audio_decoder.aac", "audio_encoder.aac" }, + { MEDIA_MIMETYPE_VIDEO_AVC, + "video_decoder.avc", "video_encoder.avc" }, + { MEDIA_MIMETYPE_VIDEO_MPEG4, + "video_decoder.mpeg4", "video_encoder.mpeg4" }, + { MEDIA_MIMETYPE_VIDEO_H263, + "video_decoder.h263", "video_encoder.h263" }, + }; + + static const size_t kNumMimeToRole = + sizeof(kMimeToRole) / sizeof(kMimeToRole[0]); + + size_t i; + for (i = 0; i < kNumMimeToRole; ++i) { + if (!strcasecmp(mime, kMimeToRole[i].mime)) { + break; + } + } + + if (i == kNumMimeToRole) { + return; + } + + const char *role = + isEncoder ? kMimeToRole[i].encoderRole + : kMimeToRole[i].decoderRole; + + if (role != NULL) { + OMX_PARAM_COMPONENTROLETYPE roleParams; + InitOMXParams(&roleParams); + + strncpy((char *)roleParams.cRole, + role, OMX_MAX_STRINGNAME_SIZE - 1); + + roleParams.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; + + status_t err = mOMX->setParameter( + mNode, OMX_IndexParamStandardComponentRole, + &roleParams, sizeof(roleParams)); + + if (err != OK) { + LOGW("[%s] Failed to set standard component role '%s'.", + mComponentName.c_str(), role); + } + } +} + +void ACodec::configureCodec( + const char *mime, const sp<AMessage> &msg) { + setComponentRole(false /* isEncoder */, mime); + + if (!strncasecmp(mime, "video/", 6)) { + int32_t width, height; + CHECK(msg->findInt32("width", &width)); + CHECK(msg->findInt32("height", &height)); + + CHECK_EQ(setupVideoDecoder(mime, width, height), + (status_t)OK); + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { + int32_t numChannels, sampleRate; + CHECK(msg->findInt32("channel-count", &numChannels)); + CHECK(msg->findInt32("sample-rate", &sampleRate)); + + CHECK_EQ(setupAACDecoder(numChannels, sampleRate), (status_t)OK); + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { + } else { + TRESPASS(); + } + + int32_t maxInputSize; + if (msg->findInt32("max-input-size", &maxInputSize)) { + CHECK_EQ(setMinBufferSize(kPortIndexInput, (size_t)maxInputSize), + (status_t)OK); + } else if (!strcmp("OMX.Nvidia.aac.decoder", mComponentName.c_str())) { + CHECK_EQ(setMinBufferSize(kPortIndexInput, 8192), // XXX + (status_t)OK); + } +} + +status_t ACodec::setMinBufferSize(OMX_U32 portIndex, size_t size) { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = portIndex; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + if (def.nBufferSize >= size) { + return OK; + } + + def.nBufferSize = size; + + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + if (err != OK) { + return err; + } + + CHECK(def.nBufferSize >= size); + + return OK; +} + +status_t ACodec::setupAACDecoder(int32_t numChannels, int32_t sampleRate) { + OMX_AUDIO_PARAM_AACPROFILETYPE profile; + InitOMXParams(&profile); + profile.nPortIndex = kPortIndexInput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + + if (err != OK) { + return err; + } + + profile.nChannels = numChannels; + profile.nSampleRate = sampleRate; + profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; + + err = mOMX->setParameter( + mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile)); + + return err; +} + +status_t ACodec::setVideoPortFormatType( + OMX_U32 portIndex, + OMX_VIDEO_CODINGTYPE compressionFormat, + OMX_COLOR_FORMATTYPE colorFormat) { + OMX_VIDEO_PARAM_PORTFORMATTYPE format; + InitOMXParams(&format); + format.nPortIndex = portIndex; + format.nIndex = 0; + bool found = false; + + OMX_U32 index = 0; + for (;;) { + format.nIndex = index; + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoPortFormat, + &format, sizeof(format)); + + if (err != OK) { + return err; + } + + // The following assertion is violated by TI's video decoder. + // CHECK_EQ(format.nIndex, index); + + if (!strcmp("OMX.TI.Video.encoder", mComponentName.c_str())) { + if (portIndex == kPortIndexInput + && colorFormat == format.eColorFormat) { + // eCompressionFormat does not seem right. + found = true; + break; + } + if (portIndex == kPortIndexOutput + && compressionFormat == format.eCompressionFormat) { + // eColorFormat does not seem right. + found = true; + break; + } + } + + if (format.eCompressionFormat == compressionFormat + && format.eColorFormat == colorFormat) { + found = true; + break; + } + + ++index; + } + + if (!found) { + return UNKNOWN_ERROR; + } + + status_t err = mOMX->setParameter( + mNode, OMX_IndexParamVideoPortFormat, + &format, sizeof(format)); + + return err; +} + +status_t ACodec::setSupportedOutputFormat() { + OMX_VIDEO_PARAM_PORTFORMATTYPE format; + InitOMXParams(&format); + format.nPortIndex = kPortIndexOutput; + format.nIndex = 0; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoPortFormat, + &format, sizeof(format)); + CHECK_EQ(err, (status_t)OK); + CHECK_EQ((int)format.eCompressionFormat, (int)OMX_VIDEO_CodingUnused); + + static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; + + CHECK(format.eColorFormat == OMX_COLOR_FormatYUV420Planar + || format.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar + || format.eColorFormat == OMX_COLOR_FormatCbYCrY + || format.eColorFormat == OMX_QCOM_COLOR_FormatYVU420SemiPlanar); + + return mOMX->setParameter( + mNode, OMX_IndexParamVideoPortFormat, + &format, sizeof(format)); +} + +status_t ACodec::setupVideoDecoder( + const char *mime, int32_t width, int32_t height) { + OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused; + if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { + compressionFormat = OMX_VIDEO_CodingAVC; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) { + compressionFormat = OMX_VIDEO_CodingMPEG4; + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { + compressionFormat = OMX_VIDEO_CodingH263; + } else { + TRESPASS(); + } + + status_t err = setVideoPortFormatType( + kPortIndexInput, compressionFormat, OMX_COLOR_FormatUnused); + + if (err != OK) { + return err; + } + + err = setSupportedOutputFormat(); + + if (err != OK) { + return err; + } + + err = setVideoFormatOnPort( + kPortIndexInput, width, height, compressionFormat); + + if (err != OK) { + return err; + } + + err = setVideoFormatOnPort( + kPortIndexOutput, width, height, OMX_VIDEO_CodingUnused); + + if (err != OK) { + return err; + } + + return OK; +} + +status_t ACodec::setVideoFormatOnPort( + OMX_U32 portIndex, + int32_t width, int32_t height, OMX_VIDEO_CODINGTYPE compressionFormat) { + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOMXParams(&def); + def.nPortIndex = portIndex; + + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + CHECK_EQ(err, (status_t)OK); + + if (portIndex == kPortIndexInput) { + // XXX Need a (much) better heuristic to compute input buffer sizes. + const size_t X = 64 * 1024; + if (def.nBufferSize < X) { + def.nBufferSize = X; + } + } + + CHECK_EQ((int)def.eDomain, (int)OMX_PortDomainVideo); + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + + if (portIndex == kPortIndexInput) { + video_def->eCompressionFormat = compressionFormat; + video_def->eColorFormat = OMX_COLOR_FormatUnused; + } + + err = mOMX->setParameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + return err; +} + +status_t ACodec::initNativeWindow() { + if (mNativeWindow != NULL) { + return mOMX->enableGraphicBuffers(mNode, kPortIndexOutput, OMX_TRUE); + } + + mOMX->enableGraphicBuffers(mNode, kPortIndexOutput, OMX_FALSE); + return OK; +} + +bool ACodec::allYourBuffersAreBelongToUs( + OMX_U32 portIndex) { + for (size_t i = 0; i < mBuffers[portIndex].size(); ++i) { + BufferInfo *info = &mBuffers[portIndex].editItemAt(i); + + if (info->mStatus != BufferInfo::OWNED_BY_US + && info->mStatus != BufferInfo::OWNED_BY_NATIVE_WINDOW) { + LOGV("[%s] Buffer %p on port %ld still has status %d", + mComponentName.c_str(), + info->mBufferID, portIndex, info->mStatus); + return false; + } + } + + return true; +} + +bool ACodec::allYourBuffersAreBelongToUs() { + return allYourBuffersAreBelongToUs(kPortIndexInput) + && allYourBuffersAreBelongToUs(kPortIndexOutput); +} + +void ACodec::deferMessage(const sp<AMessage> &msg) { + bool wasEmptyBefore = mDeferredQueue.empty(); + mDeferredQueue.push_back(msg); +} + +void ACodec::processDeferredMessages() { + List<sp<AMessage> > queue = mDeferredQueue; + mDeferredQueue.clear(); + + List<sp<AMessage> >::iterator it = queue.begin(); + while (it != queue.end()) { + onMessageReceived(*it++); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::BaseState::BaseState(ACodec *codec, const sp<AState> &parentState) + : AState(parentState), + mCodec(codec) { +} + +ACodec::BaseState::PortMode ACodec::BaseState::getPortMode(OMX_U32 portIndex) { + return KEEP_BUFFERS; +} + +bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatInputBufferFilled: + { + onInputBufferFilled(msg); + break; + } + + case kWhatOutputBufferDrained: + { + onOutputBufferDrained(msg); + break; + } + + case ACodec::kWhatOMXMessage: + { + return onOMXMessage(msg); + } + + default: + return false; + } + + return true; +} + +bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) { + int32_t type; + CHECK(msg->findInt32("type", &type)); + + IOMX::node_id nodeID; + CHECK(msg->findPointer("node", &nodeID)); + CHECK_EQ(nodeID, mCodec->mNode); + + switch (type) { + case omx_message::EVENT: + { + int32_t event, data1, data2; + CHECK(msg->findInt32("event", &event)); + CHECK(msg->findInt32("data1", &data1)); + CHECK(msg->findInt32("data2", &data2)); + + return onOMXEvent( + static_cast<OMX_EVENTTYPE>(event), + static_cast<OMX_U32>(data1), + static_cast<OMX_U32>(data2)); + } + + case omx_message::EMPTY_BUFFER_DONE: + { + IOMX::buffer_id bufferID; + CHECK(msg->findPointer("buffer", &bufferID)); + + return onOMXEmptyBufferDone(bufferID); + } + + case omx_message::FILL_BUFFER_DONE: + { + IOMX::buffer_id bufferID; + CHECK(msg->findPointer("buffer", &bufferID)); + + int32_t rangeOffset, rangeLength, flags; + int64_t timeUs; + void *platformPrivate; + void *dataPtr; + + CHECK(msg->findInt32("range_offset", &rangeOffset)); + CHECK(msg->findInt32("range_length", &rangeLength)); + CHECK(msg->findInt32("flags", &flags)); + CHECK(msg->findInt64("timestamp", &timeUs)); + CHECK(msg->findPointer("platform_private", &platformPrivate)); + CHECK(msg->findPointer("data_ptr", &dataPtr)); + + return onOMXFillBufferDone( + bufferID, + (size_t)rangeOffset, (size_t)rangeLength, + (OMX_U32)flags, + timeUs, + platformPrivate, + dataPtr); + } + + default: + TRESPASS(); + break; + } +} + +bool ACodec::BaseState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + if (event != OMX_EventError) { + LOGI("[%s] EVENT(%d, 0x%08lx, 0x%08lx)", + mCodec->mComponentName.c_str(), event, data1, data2); + + return false; + } + + LOGE("[%s] ERROR(0x%08lx, 0x%08lx)", + mCodec->mComponentName.c_str(), data1, data2); + + mCodec->changeState(mCodec->mErrorState); + + return true; +} + +bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID) { + BufferInfo *info = + mCodec->findBufferByID(kPortIndexInput, bufferID); + + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_COMPONENT); + info->mStatus = BufferInfo::OWNED_BY_US; + + PortMode mode = getPortMode(kPortIndexInput); + + switch (mode) { + case KEEP_BUFFERS: + break; + + case RESUBMIT_BUFFERS: + postFillThisBuffer(info); + break; + + default: + { + CHECK_EQ((int)mode, (int)FREE_BUFFERS); + TRESPASS(); // Not currently used + break; + } + } + + return true; +} + +void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) { + if (mCodec->mPortEOS[kPortIndexInput]) { + return; + } + + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US); + + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFillThisBuffer); + notify->setPointer("buffer-id", info->mBufferID); + + info->mData->meta()->clear(); + notify->setObject("buffer", info->mData); + + sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, mCodec->id()); + reply->setPointer("buffer-id", info->mBufferID); + + notify->setMessage("reply", reply); + + notify->post(); + + info->mStatus = BufferInfo::OWNED_BY_UPSTREAM; +} + +void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) { + IOMX::buffer_id bufferID; + CHECK(msg->findPointer("buffer-id", &bufferID)); + + sp<RefBase> obj; + int32_t err = OK; + if (!msg->findObject("buffer", &obj)) { + CHECK(msg->findInt32("err", &err)); + + obj.clear(); + } + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + + BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID); + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_UPSTREAM); + + info->mStatus = BufferInfo::OWNED_BY_US; + + PortMode mode = getPortMode(kPortIndexInput); + + switch (mode) { + case KEEP_BUFFERS: + { + if (buffer == NULL) { + mCodec->mPortEOS[kPortIndexInput] = true; + } + break; + } + + case RESUBMIT_BUFFERS: + { + if (buffer != NULL) { + CHECK(!mCodec->mPortEOS[kPortIndexInput]); + + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + + OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME; + + int32_t isCSD; + if (buffer->meta()->findInt32("csd", &isCSD) && isCSD != 0) { + flags |= OMX_BUFFERFLAG_CODECCONFIG; + } + + if (buffer != info->mData) { + if (!(flags & OMX_BUFFERFLAG_CODECCONFIG)) { + LOGV("[%s] Needs to copy input data.", + mCodec->mComponentName.c_str()); + } + + CHECK_LE(buffer->size(), info->mData->capacity()); + memcpy(info->mData->data(), buffer->data(), buffer->size()); + } + + CHECK_EQ(mCodec->mOMX->emptyBuffer( + mCodec->mNode, + bufferID, + 0, + buffer->size(), + flags, + timeUs), + (status_t)OK); + + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + + getMoreInputDataIfPossible(); + } else if (!mCodec->mPortEOS[kPortIndexInput]) { + LOGV("[%s] Signalling EOS on the input port", + mCodec->mComponentName.c_str()); + + CHECK_EQ(mCodec->mOMX->emptyBuffer( + mCodec->mNode, + bufferID, + 0, + 0, + OMX_BUFFERFLAG_EOS, + 0), + (status_t)OK); + + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + + mCodec->mPortEOS[kPortIndexInput] = true; + } + break; + + default: + CHECK_EQ((int)mode, (int)FREE_BUFFERS); + break; + } + } +} + +void ACodec::BaseState::getMoreInputDataIfPossible() { + if (mCodec->mPortEOS[kPortIndexInput]) { + return; + } + + BufferInfo *eligible = NULL; + + for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); ++i) { + BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i); + +#if 0 + if (info->mStatus == BufferInfo::OWNED_BY_UPSTREAM) { + // There's already a "read" pending. + return; + } +#endif + + if (info->mStatus == BufferInfo::OWNED_BY_US) { + eligible = info; + } + } + + if (eligible == NULL) { + return; + } + + postFillThisBuffer(eligible); +} + +bool ACodec::BaseState::onOMXFillBufferDone( + IOMX::buffer_id bufferID, + size_t rangeOffset, size_t rangeLength, + OMX_U32 flags, + int64_t timeUs, + void *platformPrivate, + void *dataPtr) { + ssize_t index; + BufferInfo *info = + mCodec->findBufferByID(kPortIndexOutput, bufferID, &index); + + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_COMPONENT); + + info->mStatus = BufferInfo::OWNED_BY_US; + + PortMode mode = getPortMode(kPortIndexOutput); + + switch (mode) { + case KEEP_BUFFERS: + break; + + case RESUBMIT_BUFFERS: + { + if (rangeLength == 0) { + if (!(flags & OMX_BUFFERFLAG_EOS)) { + CHECK_EQ(mCodec->mOMX->fillBuffer( + mCodec->mNode, info->mBufferID), + (status_t)OK); + + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + } + } else { + if (mCodec->mNativeWindow == NULL) { + info->mData->setRange(rangeOffset, rangeLength); + } + + info->mData->meta()->setInt64("timeUs", timeUs); + + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatDrainThisBuffer); + notify->setPointer("buffer-id", info->mBufferID); + notify->setObject("buffer", info->mData); + + sp<AMessage> reply = + new AMessage(kWhatOutputBufferDrained, mCodec->id()); + + reply->setPointer("buffer-id", info->mBufferID); + + notify->setMessage("reply", reply); + + notify->post(); + + info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM; + } + + if (flags & OMX_BUFFERFLAG_EOS) { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatEOS); + notify->post(); + + mCodec->mPortEOS[kPortIndexOutput] = true; + } + break; + } + + default: + { + CHECK_EQ((int)mode, (int)FREE_BUFFERS); + + CHECK_EQ((status_t)OK, + mCodec->freeBuffer(kPortIndexOutput, index)); + break; + } + } + + return true; +} + +void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) { + IOMX::buffer_id bufferID; + CHECK(msg->findPointer("buffer-id", &bufferID)); + + ssize_t index; + BufferInfo *info = + mCodec->findBufferByID(kPortIndexOutput, bufferID, &index); + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_DOWNSTREAM); + + int32_t render; + if (mCodec->mNativeWindow != NULL + && msg->findInt32("render", &render) && render != 0) { + // The client wants this buffer to be rendered. + + CHECK_EQ(mCodec->mNativeWindow->queueBuffer( + mCodec->mNativeWindow.get(), + info->mGraphicBuffer.get()), + 0); + + info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; + } else { + info->mStatus = BufferInfo::OWNED_BY_US; + } + + PortMode mode = getPortMode(kPortIndexOutput); + + switch (mode) { + case KEEP_BUFFERS: + { + // XXX fishy, revisit!!! What about the FREE_BUFFERS case below? + + if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) { + // We cannot resubmit the buffer we just rendered, dequeue + // the spare instead. + + info = mCodec->dequeueBufferFromNativeWindow(); + } + break; + } + + case RESUBMIT_BUFFERS: + { + if (!mCodec->mPortEOS[kPortIndexOutput]) { + if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) { + // We cannot resubmit the buffer we just rendered, dequeue + // the spare instead. + + info = mCodec->dequeueBufferFromNativeWindow(); + } + + CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID), + (status_t)OK); + + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + } + break; + } + + default: + { + CHECK_EQ((int)mode, (int)FREE_BUFFERS); + + CHECK_EQ((status_t)OK, + mCodec->freeBuffer(kPortIndexOutput, index)); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::UninitializedState::UninitializedState(ACodec *codec) + : BaseState(codec) { +} + +bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case ACodec::kWhatSetup: + { + onSetup(msg); + + handled = true; + break; + } + + case ACodec::kWhatShutdown: + { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatShutdownCompleted); + notify->post(); + + handled = true; + } + + case ACodec::kWhatFlush: + { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->post(); + + handled = true; + } + + default: + return BaseState::onMessageReceived(msg); + } + + return handled; +} + +void ACodec::UninitializedState::onSetup( + const sp<AMessage> &msg) { + OMXClient client; + CHECK_EQ(client.connect(), (status_t)OK); + + sp<IOMX> omx = client.interface(); + + AString mime; + CHECK(msg->findString("mime", &mime)); + + AString componentName; + + if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) { + componentName = "OMX.Nvidia.h264.decode"; + } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) { + componentName = "OMX.Nvidia.aac.decoder"; + } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_AUDIO_MPEG)) { + componentName = "OMX.Nvidia.mp3.decoder"; + } else { + TRESPASS(); + } + + sp<CodecObserver> observer = new CodecObserver; + + IOMX::node_id node; + CHECK_EQ(omx->allocateNode(componentName.c_str(), observer, &node), + (status_t)OK); + + sp<AMessage> notify = new AMessage(kWhatOMXMessage, mCodec->id()); + observer->setNotificationMessage(notify); + + mCodec->mComponentName = componentName; + mCodec->mOMX = omx; + mCodec->mNode = node; + + mCodec->configureCodec(mime.c_str(), msg); + + sp<RefBase> obj; + if (msg->findObject("surface", &obj)) { + mCodec->mNativeWindow = static_cast<Surface *>(obj.get()); + } + + CHECK_EQ((status_t)OK, mCodec->initNativeWindow()); + + CHECK_EQ(omx->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle), + (status_t)OK); + + mCodec->changeState(mCodec->mLoadedToIdleState); +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::LoadedToIdleState::LoadedToIdleState(ACodec *codec) + : BaseState(codec) { +} + +void ACodec::LoadedToIdleState::stateEntered() { + LOGI("[%s] Now Loaded->Idle", mCodec->mComponentName.c_str()); + + CHECK_EQ(allocateBuffers(), (status_t)OK); +} + +status_t ACodec::LoadedToIdleState::allocateBuffers() { + status_t err = mCodec->allocateBuffersOnPort(kPortIndexInput); + + if (err != OK) { + return err; + } + + return mCodec->allocateBuffersOnPort(kPortIndexOutput); +} + +bool ACodec::LoadedToIdleState::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatShutdown: + { + mCodec->deferMessage(msg); + return true; + } + + default: + return BaseState::onMessageReceived(msg); + } +} + +bool ACodec::LoadedToIdleState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); + CHECK_EQ(data2, (OMX_U32)OMX_StateIdle); + + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandStateSet, OMX_StateExecuting), + (status_t)OK); + + mCodec->changeState(mCodec->mIdleToExecutingState); + + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::IdleToExecutingState::IdleToExecutingState(ACodec *codec) + : BaseState(codec) { +} + +void ACodec::IdleToExecutingState::stateEntered() { + LOGI("[%s] Now Idle->Executing", mCodec->mComponentName.c_str()); +} + +bool ACodec::IdleToExecutingState::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatShutdown: + { + mCodec->deferMessage(msg); + return true; + } + + default: + return BaseState::onMessageReceived(msg); + } +} + +bool ACodec::IdleToExecutingState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); + CHECK_EQ(data2, (OMX_U32)OMX_StateExecuting); + + mCodec->mExecutingState->resume(); + mCodec->changeState(mCodec->mExecutingState); + + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::ExecutingState::ExecutingState(ACodec *codec) + : BaseState(codec) { +} + +ACodec::BaseState::PortMode ACodec::ExecutingState::getPortMode( + OMX_U32 portIndex) { + return RESUBMIT_BUFFERS; +} + +void ACodec::ExecutingState::submitOutputBuffers() { + for (size_t i = 0; i < mCodec->mBuffers[kPortIndexOutput].size(); ++i) { + BufferInfo *info = &mCodec->mBuffers[kPortIndexOutput].editItemAt(i); + + if (mCodec->mNativeWindow != NULL) { + CHECK(info->mStatus == BufferInfo::OWNED_BY_US + || info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW); + + if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) { + continue; + } + + status_t err = mCodec->mNativeWindow->lockBuffer( + mCodec->mNativeWindow.get(), + info->mGraphicBuffer.get()); + CHECK_EQ(err, (status_t)OK); + } else { + CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US); + } + + CHECK_EQ(mCodec->mOMX->fillBuffer(mCodec->mNode, info->mBufferID), + (status_t)OK); + + info->mStatus = BufferInfo::OWNED_BY_COMPONENT; + } +} + +void ACodec::ExecutingState::resume() { + submitOutputBuffers(); + + // Post the first input buffer. + CHECK_GT(mCodec->mBuffers[kPortIndexInput].size(), 0u); + BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(0); + + postFillThisBuffer(info); +} + +void ACodec::ExecutingState::stateEntered() { + LOGI("[%s] Now Executing", mCodec->mComponentName.c_str()); + + mCodec->processDeferredMessages(); +} + +bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case kWhatShutdown: + { + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandStateSet, OMX_StateIdle), + (status_t)OK); + + mCodec->changeState(mCodec->mExecutingToIdleState); + + handled = true; + break; + } + + case kWhatFlush: + { + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandFlush, OMX_ALL), + (status_t)OK); + + mCodec->changeState(mCodec->mFlushingState); + + handled = true; + break; + } + + case kWhatResume: + { + resume(); + + handled = true; + break; + } + + default: + handled = BaseState::onMessageReceived(msg); + break; + } + + return handled; +} + +bool ACodec::ExecutingState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventPortSettingsChanged: + { + CHECK_EQ(data1, (OMX_U32)kPortIndexOutput); + + if (data2 == OMX_IndexParamPortDefinition) { + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, + OMX_CommandPortDisable, kPortIndexOutput), + (status_t)OK); + + if (mCodec->mNativeWindow != NULL) { + CHECK_EQ((status_t)OK, + mCodec->freeOutputBuffersOwnedByNativeWindow()); + } + + mCodec->changeState(mCodec->mOutputPortSettingsChangedState); + } else { + LOGV("[%s] OMX_EventPortSettingsChanged 0x%08lx", + mCodec->mComponentName.c_str(), data2); + } + + return true; + } + + case OMX_EventBufferFlag: + { + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::OutputPortSettingsChangedState::OutputPortSettingsChangedState( + ACodec *codec) + : BaseState(codec) { +} + +ACodec::BaseState::PortMode ACodec::OutputPortSettingsChangedState::getPortMode( + OMX_U32 portIndex) { + if (portIndex == kPortIndexOutput) { + return FREE_BUFFERS; + } + + CHECK_EQ(portIndex, (OMX_U32)kPortIndexInput); + + return RESUBMIT_BUFFERS; +} + +bool ACodec::OutputPortSettingsChangedState::onMessageReceived( + const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case kWhatFlush: + case kWhatShutdown: + { + mCodec->deferMessage(msg); + handled = true; + break; + } + + default: + handled = BaseState::onMessageReceived(msg); + break; + } + + return handled; +} + +void ACodec::OutputPortSettingsChangedState::stateEntered() { + LOGI("[%s] Now handling output port settings change", + mCodec->mComponentName.c_str()); +} + +bool ACodec::OutputPortSettingsChangedState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + if (data1 == (OMX_U32)OMX_CommandPortDisable) { + CHECK_EQ(data2, (OMX_U32)kPortIndexOutput); + + LOGV("[%s] Output port now disabled.", + mCodec->mComponentName.c_str()); + + CHECK(mCodec->mBuffers[kPortIndexOutput].isEmpty()); + mCodec->mDealer[kPortIndexOutput].clear(); + + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandPortEnable, kPortIndexOutput), + (status_t)OK); + + CHECK_EQ(mCodec->allocateBuffersOnPort(kPortIndexOutput), + (status_t)OK); + + return true; + } else if (data1 == (OMX_U32)OMX_CommandPortEnable) { + CHECK_EQ(data2, (OMX_U32)kPortIndexOutput); + + LOGV("[%s] Output port now reenabled.", + mCodec->mComponentName.c_str()); + + mCodec->mExecutingState->submitOutputBuffers(); + mCodec->changeState(mCodec->mExecutingState); + + return true; + } + + return false; + } + + default: + return false; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::ExecutingToIdleState::ExecutingToIdleState(ACodec *codec) + : BaseState(codec) { +} + +bool ACodec::ExecutingToIdleState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case kWhatFlush: + { + // Don't send me a flush request if you previously wanted me + // to shutdown. + TRESPASS(); + break; + } + + case kWhatShutdown: + { + // We're already doing that... + + handled = true; + break; + } + + default: + handled = BaseState::onMessageReceived(msg); + break; + } + + return handled; +} + +void ACodec::ExecutingToIdleState::stateEntered() { + LOGI("[%s] Now Executing->Idle", mCodec->mComponentName.c_str()); +} + +bool ACodec::ExecutingToIdleState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); + CHECK_EQ(data2, (OMX_U32)OMX_StateIdle); + + changeStateIfWeOwnAllBuffers(); + + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } +} +void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() { + if (mCodec->allYourBuffersAreBelongToUs()) { + CHECK_EQ(mCodec->mOMX->sendCommand( + mCodec->mNode, OMX_CommandStateSet, OMX_StateLoaded), + (status_t)OK); + + CHECK_EQ(mCodec->freeBuffersOnPort(kPortIndexInput), (status_t)OK); + CHECK_EQ(mCodec->freeBuffersOnPort(kPortIndexOutput), (status_t)OK); + + mCodec->changeState(mCodec->mIdleToLoadedState); + } +} + +void ACodec::ExecutingToIdleState::onInputBufferFilled( + const sp<AMessage> &msg) { + BaseState::onInputBufferFilled(msg); + + changeStateIfWeOwnAllBuffers(); +} + +void ACodec::ExecutingToIdleState::onOutputBufferDrained( + const sp<AMessage> &msg) { + BaseState::onOutputBufferDrained(msg); + + changeStateIfWeOwnAllBuffers(); +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::IdleToLoadedState::IdleToLoadedState(ACodec *codec) + : BaseState(codec) { +} + +bool ACodec::IdleToLoadedState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case kWhatShutdown: + { + // We're already doing that... + + handled = true; + break; + } + + case kWhatFlush: + { + // Don't send me a flush request if you previously wanted me + // to shutdown. + TRESPASS(); + break; + } + + default: + handled = BaseState::onMessageReceived(msg); + break; + } + + return handled; +} + +void ACodec::IdleToLoadedState::stateEntered() { + LOGI("[%s] Now Idle->Loaded", mCodec->mComponentName.c_str()); +} + +bool ACodec::IdleToLoadedState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + CHECK_EQ(data1, (OMX_U32)OMX_CommandStateSet); + CHECK_EQ(data2, (OMX_U32)OMX_StateLoaded); + + LOGI("[%s] Now Loaded", mCodec->mComponentName.c_str()); + + CHECK_EQ(mCodec->mOMX->freeNode(mCodec->mNode), (status_t)OK); + + mCodec->mNativeWindow.clear(); + mCodec->mNode = NULL; + mCodec->mOMX.clear(); + mCodec->mComponentName.clear(); + + mCodec->changeState(mCodec->mUninitializedState); + + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatShutdownCompleted); + notify->post(); + + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::ErrorState::ErrorState(ACodec *codec) + : BaseState(codec) { +} + +bool ACodec::ErrorState::onMessageReceived(const sp<AMessage> &msg) { + return BaseState::onMessageReceived(msg); +} + +void ACodec::ErrorState::stateEntered() { + LOGI("[%s] Now in ErrorState", mCodec->mComponentName.c_str()); +} + +bool ACodec::ErrorState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + LOGI("EVENT(%d, 0x%08lx, 0x%08lx)", event, data1, data2); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +ACodec::FlushingState::FlushingState(ACodec *codec) + : BaseState(codec) { +} + +void ACodec::FlushingState::stateEntered() { + LOGI("[%s] Now Flushing", mCodec->mComponentName.c_str()); + + mFlushComplete[kPortIndexInput] = mFlushComplete[kPortIndexOutput] = false; +} + +bool ACodec::FlushingState::onMessageReceived(const sp<AMessage> &msg) { + bool handled = false; + + switch (msg->what()) { + case kWhatShutdown: + { + mCodec->deferMessage(msg); + break; + } + + case kWhatFlush: + { + // We're already doing this right now. + handled = true; + break; + } + + default: + handled = BaseState::onMessageReceived(msg); + break; + } + + return handled; +} + +bool ACodec::FlushingState::onOMXEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + switch (event) { + case OMX_EventCmdComplete: + { + CHECK_EQ(data1, (OMX_U32)OMX_CommandFlush); + + if (data2 == kPortIndexInput || data2 == kPortIndexOutput) { + CHECK(!mFlushComplete[data2]); + mFlushComplete[data2] = true; + } else { + CHECK_EQ(data2, OMX_ALL); + CHECK(mFlushComplete[kPortIndexInput]); + CHECK(mFlushComplete[kPortIndexOutput]); + + changeStateIfWeOwnAllBuffers(); + } + + return true; + } + + default: + return BaseState::onOMXEvent(event, data1, data2); + } + + return true; +} + +void ACodec::FlushingState::onOutputBufferDrained(const sp<AMessage> &msg) { + BaseState::onOutputBufferDrained(msg); + + changeStateIfWeOwnAllBuffers(); +} + +void ACodec::FlushingState::onInputBufferFilled(const sp<AMessage> &msg) { + BaseState::onInputBufferFilled(msg); + + changeStateIfWeOwnAllBuffers(); +} + +void ACodec::FlushingState::changeStateIfWeOwnAllBuffers() { + if (mFlushComplete[kPortIndexInput] + && mFlushComplete[kPortIndexOutput] + && mCodec->allYourBuffersAreBelongToUs()) { + sp<AMessage> notify = mCodec->mNotify->dup(); + notify->setInt32("what", ACodec::kWhatFlushCompleted); + notify->post(); + + mCodec->mPortEOS[kPortIndexInput] = + mCodec->mPortEOS[kPortIndexOutput] = false; + + mCodec->changeState(mCodec->mExecutingState); + } +} + +} // namespace android + diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index db23836..2d486e3 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) include frameworks/base/media/libstagefright/codecs/common/Config.mk LOCAL_SRC_FILES:= \ + ACodec.cpp \ AMRExtractor.cpp \ AMRWriter.cpp \ AudioPlayer.cpp \ diff --git a/media/libstagefright/foundation/AHierarchicalStateMachine.cpp b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp new file mode 100644 index 0000000..30286d8 --- /dev/null +++ b/media/libstagefright/foundation/AHierarchicalStateMachine.cpp @@ -0,0 +1,97 @@ +#include <media/stagefright/foundation/AHierarchicalStateMachine.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <utils/Vector.h> + +namespace android { + +AState::AState(const sp<AState> &parentState) + : mParentState(parentState) { +} + +AState::~AState() { +} + +sp<AState> AState::parentState() { + return mParentState; +} + +void AState::stateEntered() { +} + +void AState::stateExited() { +} + +//////////////////////////////////////////////////////////////////////////////// + +AHierarchicalStateMachine::AHierarchicalStateMachine() { +} + +AHierarchicalStateMachine::~AHierarchicalStateMachine() { +} + +void AHierarchicalStateMachine::onMessageReceived(const sp<AMessage> &msg) { + sp<AState> save = mState; + + sp<AState> cur = mState; + while (cur != NULL && !cur->onMessageReceived(msg)) { + // If you claim not to have handled the message you shouldn't + // have called setState... + CHECK(save == mState); + + cur = cur->parentState(); + } + + if (cur != NULL) { + return; + } + + LOGW("Warning message %s unhandled in root state.", + msg->debugString().c_str()); +} + +void AHierarchicalStateMachine::changeState(const sp<AState> &state) { + if (state == mState) { + // Quick exit for the easy case. + return; + } + + Vector<sp<AState> > A; + sp<AState> cur = mState; + for (;;) { + A.push(cur); + if (cur == NULL) { + break; + } + cur = cur->parentState(); + } + + Vector<sp<AState> > B; + cur = state; + for (;;) { + B.push(cur); + if (cur == NULL) { + break; + } + cur = cur->parentState(); + } + + // Remove the common tail. + while (A.size() > 0 && B.size() > 0 && A.top() == B.top()) { + A.pop(); + B.pop(); + } + + mState = state; + + for (size_t i = 0; i < A.size(); ++i) { + A.editItemAt(i)->stateExited(); + } + + for (size_t i = B.size(); i-- > 0;) { + B.editItemAt(i)->stateEntered(); + } +} + +} // namespace android diff --git a/media/libstagefright/foundation/Android.mk b/media/libstagefright/foundation/Android.mk index a4d4809..4e07f6f 100644 --- a/media/libstagefright/foundation/Android.mk +++ b/media/libstagefright/foundation/Android.mk @@ -1,16 +1,17 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - AAtomizer.cpp \ - ABitReader.cpp \ - ABuffer.cpp \ - AHandler.cpp \ - ALooper.cpp \ - ALooperRoster.cpp \ - AMessage.cpp \ - AString.cpp \ - base64.cpp \ +LOCAL_SRC_FILES:= \ + AAtomizer.cpp \ + ABitReader.cpp \ + ABuffer.cpp \ + AHandler.cpp \ + AHierarchicalStateMachine.cpp \ + ALooper.cpp \ + ALooperRoster.cpp \ + AMessage.cpp \ + AString.cpp \ + base64.cpp \ hexdump.cpp LOCAL_C_INCLUDES:= \ diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp index a559b21..de6346b 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.cpp +++ b/media/libstagefright/mpeg2ts/ATSParser.cpp @@ -43,19 +43,21 @@ namespace android { static const size_t kTSPacketSize = 188; struct ATSParser::Program : public RefBase { - Program(unsigned programMapPID); + Program(ATSParser *parser, unsigned programMapPID); bool parsePID( unsigned pid, unsigned payload_unit_start_indicator, ABitReader *br); - void signalDiscontinuity(bool isASeek); + void signalDiscontinuity(DiscontinuityType type); + void signalEOS(status_t finalResult); sp<MediaSource> getSource(SourceType type); int64_t convertPTSToTimestamp(uint64_t PTS); private: + ATSParser *mParser; unsigned mProgramMapPID; KeyedVector<unsigned, sp<Stream> > mStreams; bool mFirstPTSValid; @@ -73,7 +75,8 @@ struct ATSParser::Stream : public RefBase { unsigned payload_unit_start_indicator, ABitReader *br); - void signalDiscontinuity(bool isASeek); + void signalDiscontinuity(DiscontinuityType type); + void signalEOS(status_t finalResult); sp<MediaSource> getSource(SourceType type); @@ -105,8 +108,9 @@ private: //////////////////////////////////////////////////////////////////////////////// -ATSParser::Program::Program(unsigned programMapPID) - : mProgramMapPID(programMapPID), +ATSParser::Program::Program(ATSParser *parser, unsigned programMapPID) + : mParser(parser), + mProgramMapPID(programMapPID), mFirstPTSValid(false), mFirstPTS(0) { } @@ -135,9 +139,15 @@ bool ATSParser::Program::parsePID( return true; } -void ATSParser::Program::signalDiscontinuity(bool isASeek) { +void ATSParser::Program::signalDiscontinuity(DiscontinuityType type) { for (size_t i = 0; i < mStreams.size(); ++i) { - mStreams.editValueAt(i)->signalDiscontinuity(isASeek); + mStreams.editValueAt(i)->signalDiscontinuity(type); + } +} + +void ATSParser::Program::signalEOS(status_t finalResult) { + for (size_t i = 0; i < mStreams.size(); ++i) { + mStreams.editValueAt(i)->signalEOS(finalResult); } } @@ -155,7 +165,7 @@ void ATSParser::Program::parseProgramMap(ABitReader *br) { unsigned section_length = br->getBits(12); LOGV(" section_length = %u", section_length); - CHECK((section_length & 0xc00) == 0); + CHECK_EQ(section_length & 0xc00, 0u); CHECK_LE(section_length, 1021u); MY_LOGV(" program_number = %u", br->getBits(16)); @@ -170,7 +180,7 @@ void ATSParser::Program::parseProgramMap(ABitReader *br) { unsigned program_info_length = br->getBits(12); LOGV(" program_info_length = %u", program_info_length); - CHECK((program_info_length & 0xc00) == 0); + CHECK_EQ(program_info_length & 0xc00, 0u); br->skipBits(program_info_length * 8); // skip descriptors @@ -194,7 +204,7 @@ void ATSParser::Program::parseProgramMap(ABitReader *br) { unsigned ES_info_length = br->getBits(12); LOGV(" ES_info_length = %u", ES_info_length); - CHECK((ES_info_length & 0xc00) == 0); + CHECK_EQ(ES_info_length & 0xc00, 0u); CHECK_GE(infoBytesRemaining - 5, ES_info_length); @@ -305,7 +315,7 @@ void ATSParser::Stream::parse( } size_t payloadSizeBits = br->numBitsLeft(); - CHECK((payloadSizeBits % 8) == 0); + CHECK_EQ(payloadSizeBits % 8, 0u); CHECK_LE(mBuffer->size() + payloadSizeBits / 8, mBuffer->capacity()); @@ -313,27 +323,45 @@ void ATSParser::Stream::parse( mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8); } -void ATSParser::Stream::signalDiscontinuity(bool isASeek) { - isASeek = false; // Always signal a "real" discontinuity - +void ATSParser::Stream::signalDiscontinuity(DiscontinuityType type) { mPayloadStarted = false; mBuffer->setRange(0, 0); - mQueue.clear(); + switch (type) { + case DISCONTINUITY_HTTPLIVE: + { + mQueue.clear(true); - if (isASeek) { - // This is only a "minor" discontinuity, we stay within the same - // bitstream. + if (mStreamType == 0x1b && mSource != NULL) { + // Don't signal discontinuities on audio streams. + mSource->queueDiscontinuity(); + } + break; + } + + case DISCONTINUITY_SEEK: + case DISCONTINUITY_FORMATCHANGE: + { + bool isASeek = (type == DISCONTINUITY_SEEK); - if (mSource != NULL) { - mSource->clear(); + mQueue.clear(!isASeek); + + if (mSource != NULL) { + mSource->clear(); + mSource->queueDiscontinuity(); + } + break; } - return; + + default: + TRESPASS(); + break; } +} - if (mStreamType == 0x1b && mSource != NULL) { - // Don't signal discontinuities on audio streams. - mSource->queueDiscontinuity(); +void ATSParser::Stream::signalEOS(status_t finalResult) { + if (mSource != NULL) { + mSource->signalEOS(finalResult); } } @@ -478,7 +506,7 @@ void ATSParser::Stream::parsePES(ABitReader *br) { br->data(), br->numBitsLeft() / 8); size_t payloadSizeBits = br->numBitsLeft(); - CHECK((payloadSizeBits % 8) == 0); + CHECK_EQ(payloadSizeBits % 8, 0u); LOGV("There's %d bytes of payload.", payloadSizeBits / 8); } @@ -526,6 +554,7 @@ void ATSParser::Stream::onPayloadData( if (meta != NULL) { LOGV("created source!"); mSource = new AnotherPacketSource(meta); + mSource->queueAccessUnit(accessUnit); } } else if (mQueue.getFormat() != NULL) { @@ -561,9 +590,17 @@ void ATSParser::feedTSPacket(const void *data, size_t size) { parseTS(&br); } -void ATSParser::signalDiscontinuity(bool isASeek) { +void ATSParser::signalDiscontinuity(DiscontinuityType type) { + for (size_t i = 0; i < mPrograms.size(); ++i) { + mPrograms.editItemAt(i)->signalDiscontinuity(type); + } +} + +void ATSParser::signalEOS(status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + for (size_t i = 0; i < mPrograms.size(); ++i) { - mPrograms.editItemAt(i)->signalDiscontinuity(isASeek); + mPrograms.editItemAt(i)->signalEOS(finalResult); } } @@ -581,7 +618,7 @@ void ATSParser::parseProgramAssociationTable(ABitReader *br) { unsigned section_length = br->getBits(12); LOGV(" section_length = %u", section_length); - CHECK((section_length & 0xc00) == 0); + CHECK_EQ(section_length & 0xc00, 0u); MY_LOGV(" transport_stream_id = %u", br->getBits(16)); MY_LOGV(" reserved = %u", br->getBits(2)); @@ -606,7 +643,7 @@ void ATSParser::parseProgramAssociationTable(ABitReader *br) { LOGV(" program_map_PID = 0x%04x", programMapPID); - mPrograms.push(new Program(programMapPID)); + mPrograms.push(new Program(this, programMapPID)); } } diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h index 11b1de4..ef78c77 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.h +++ b/media/libstagefright/mpeg2ts/ATSParser.h @@ -21,19 +21,28 @@ #include <sys/types.h> #include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AMessage.h> #include <utils/Vector.h> #include <utils/RefBase.h> namespace android { struct ABitReader; +struct ABuffer; struct MediaSource; struct ATSParser : public RefBase { + enum DiscontinuityType { + DISCONTINUITY_HTTPLIVE, + DISCONTINUITY_SEEK, + DISCONTINUITY_FORMATCHANGE + }; + ATSParser(); void feedTSPacket(const void *data, size_t size); - void signalDiscontinuity(bool isASeek = false); + void signalDiscontinuity(DiscontinuityType type); + void signalEOS(status_t finalResult); enum SourceType { AVC_VIDEO, diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp index ea747c8..7a1d5b0 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp @@ -48,6 +48,32 @@ sp<MetaData> AnotherPacketSource::getFormat() { return mFormat; } +status_t AnotherPacketSource::dequeueAccessUnit(sp<ABuffer> *buffer) { + buffer->clear(); + + Mutex::Autolock autoLock(mLock); + while (mEOSResult == OK && mBuffers.empty()) { + mCondition.wait(mLock); + } + + if (!mBuffers.empty()) { + *buffer = *mBuffers.begin(); + mBuffers.erase(mBuffers.begin()); + + int32_t discontinuity; + if ((*buffer)->meta()->findInt32("discontinuity", &discontinuity) + && discontinuity) { + buffer->clear(); + + return INFO_DISCONTINUITY; + } + + return OK; + } + + return mEOSResult; +} + status_t AnotherPacketSource::read( MediaBuffer **out, const ReadOptions *) { *out = NULL; @@ -66,9 +92,8 @@ status_t AnotherPacketSource::read( && discontinuity) { return INFO_DISCONTINUITY; } else { - uint64_t timeUs; - CHECK(buffer->meta()->findInt64( - "time", (int64_t *)&timeUs)); + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size()); mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs); @@ -92,7 +117,7 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) { } int64_t timeUs; - CHECK(buffer->meta()->findInt64("time", &timeUs)); + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); LOGV("queueAccessUnit timeUs=%lld us (%.2f secs)", timeUs, timeUs / 1E6); Mutex::Autolock autoLock(mLock); @@ -134,4 +159,19 @@ bool AnotherPacketSource::hasBufferAvailable(status_t *finalResult) { return false; } +status_t AnotherPacketSource::nextBufferTime(int64_t *timeUs) { + *timeUs = 0; + + Mutex::Autolock autoLock(mLock); + + if (mBuffers.empty()) { + return mEOSResult != OK ? mEOSResult : -EWOULDBLOCK; + } + + sp<ABuffer> buffer = *mBuffers.begin(); + CHECK(buffer->meta()->findInt64("timeUs", timeUs)); + + return OK; +} + } // namespace android diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h index 6999175..2bc7404 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h @@ -39,12 +39,16 @@ struct AnotherPacketSource : public MediaSource { bool hasBufferAvailable(status_t *finalResult); + status_t nextBufferTime(int64_t *timeUs); + void queueAccessUnit(const sp<ABuffer> &buffer); void queueDiscontinuity(); void signalEOS(status_t result); void clear(); + status_t dequeueAccessUnit(sp<ABuffer> *buffer); + protected: virtual ~AnotherPacketSource(); diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp index 4b4490d..4e7759d 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.cpp +++ b/media/libstagefright/mpeg2ts/ESQueue.cpp @@ -40,13 +40,16 @@ sp<MetaData> ElementaryStreamQueue::getFormat() { return mFormat; } -void ElementaryStreamQueue::clear() { +void ElementaryStreamQueue::clear(bool clearFormat) { if (mBuffer != NULL) { mBuffer->setRange(0, 0); } mRangeInfos.clear(); - mFormat.clear(); + + if (clearFormat) { + mFormat.clear(); + } } static bool IsSeeminglyValidADTSHeader(const uint8_t *ptr, size_t size) { @@ -289,7 +292,7 @@ sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() { mBuffer->setRange(0, mBuffer->size() - offset); if (timeUs >= 0) { - accessUnit->meta()->setInt64("time", timeUs); + accessUnit->meta()->setInt64("timeUs", timeUs); } else { LOGW("no time for AAC access unit"); } @@ -470,7 +473,7 @@ sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() { int64_t timeUs = fetchTimestamp(nextScan); CHECK_GE(timeUs, 0ll); - accessUnit->meta()->setInt64("time", timeUs); + accessUnit->meta()->setInt64("timeUs", timeUs); if (mFormat == NULL) { mFormat = MakeAVCCodecSpecificData(accessUnit); diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h index adce153..5b7957e 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.h +++ b/media/libstagefright/mpeg2ts/ESQueue.h @@ -35,7 +35,7 @@ struct ElementaryStreamQueue { ElementaryStreamQueue(Mode mode); status_t appendData(const void *data, size_t size, int64_t timeUs); - void clear(); + void clear(bool clearFormat); sp<ABuffer> dequeueAccessUnit(); diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp index 600116e..a1f0796 100644 --- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp +++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp @@ -214,7 +214,7 @@ status_t MPEG2TSExtractor::feedMore() { if (isDiscontinuity(packet, n)) { LOGI("XXX discontinuity detected"); - mParser->signalDiscontinuity(); + mParser->signalDiscontinuity(ATSParser::DISCONTINUITY_HTTPLIVE); } else if (n < (ssize_t)kTSPacketSize) { return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; } else { |