From 840667883fd09d44015716d79bc3ac4d60edc0f0 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Tue, 16 Aug 2011 09:34:26 -0700 Subject: experimental support for fragmented mp4 playback in nuplayer cherry picked from change 170999 Change-Id: I407775f0290154ad4961134839a15c9f296424c0 --- media/libmediaplayerservice/nuplayer/Android.mk | 3 + .../nuplayer/GenericSource.cpp | 2 +- .../libmediaplayerservice/nuplayer/GenericSource.h | 3 +- .../nuplayer/HTTPLiveSource.cpp | 2 +- .../nuplayer/HTTPLiveSource.h | 3 +- media/libmediaplayerservice/nuplayer/NuPlayer.cpp | 41 +- media/libmediaplayerservice/nuplayer/NuPlayer.h | 6 +- .../nuplayer/NuPlayerDecoder.cpp | 41 +- .../nuplayer/NuPlayerDecoder.h | 2 +- .../nuplayer/NuPlayerSource.h | 4 +- .../libmediaplayerservice/nuplayer/RTSPSource.cpp | 2 +- media/libmediaplayerservice/nuplayer/RTSPSource.h | 3 +- .../nuplayer/StreamingSource.cpp | 2 +- .../nuplayer/StreamingSource.h | 3 +- .../nuplayer/mp4/MP4Source.cpp | 132 ++ .../libmediaplayerservice/nuplayer/mp4/MP4Source.h | 52 + .../libmediaplayerservice/nuplayer/mp4/Parser.cpp | 1643 ++++++++++++++++++++ media/libmediaplayerservice/nuplayer/mp4/Parser.h | 252 +++ .../nuplayer/mp4/TrackFragment.cpp | 383 +++++ .../nuplayer/mp4/TrackFragment.h | 122 ++ 20 files changed, 2654 insertions(+), 47 deletions(-) create mode 100644 media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp create mode 100644 media/libmediaplayerservice/nuplayer/mp4/MP4Source.h create mode 100644 media/libmediaplayerservice/nuplayer/mp4/Parser.cpp create mode 100644 media/libmediaplayerservice/nuplayer/mp4/Parser.h create mode 100644 media/libmediaplayerservice/nuplayer/mp4/TrackFragment.cpp create mode 100644 media/libmediaplayerservice/nuplayer/mp4/TrackFragment.h (limited to 'media/libmediaplayerservice/nuplayer') diff --git a/media/libmediaplayerservice/nuplayer/Android.mk b/media/libmediaplayerservice/nuplayer/Android.mk index f97ba57..f469054 100644 --- a/media/libmediaplayerservice/nuplayer/Android.mk +++ b/media/libmediaplayerservice/nuplayer/Android.mk @@ -11,6 +11,9 @@ LOCAL_SRC_FILES:= \ NuPlayerStreamListener.cpp \ RTSPSource.cpp \ StreamingSource.cpp \ + mp4/MP4Source.cpp \ + mp4/Parser.cpp \ + mp4/TrackFragment.cpp \ LOCAL_C_INCLUDES := \ $(TOP)/frameworks/av/media/libstagefright/httplive \ diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp index 99569c9..f0c3240 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp +++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp @@ -128,7 +128,7 @@ status_t NuPlayer::GenericSource::feedMoreTSData() { return OK; } -sp NuPlayer::GenericSource::getFormat(bool audio) { +sp NuPlayer::GenericSource::getFormatMeta(bool audio) { sp source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; if (source == NULL) { diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h index aaa5876..e50b855 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.h +++ b/media/libmediaplayerservice/nuplayer/GenericSource.h @@ -43,7 +43,6 @@ struct NuPlayer::GenericSource : public NuPlayer::Source { virtual status_t feedMoreTSData(); - virtual sp getFormat(bool audio); virtual status_t dequeueAccessUnit(bool audio, sp *accessUnit); virtual status_t getDuration(int64_t *durationUs); @@ -53,6 +52,8 @@ struct NuPlayer::GenericSource : public NuPlayer::Source { protected: virtual ~GenericSource(); + virtual sp getFormatMeta(bool audio); + private: struct Track { sp mSource; diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp index 22b8847..1e98f35 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -81,7 +81,7 @@ void NuPlayer::HTTPLiveSource::start() { mTSParser = new ATSParser; } -sp NuPlayer::HTTPLiveSource::getFormat(bool audio) { +sp NuPlayer::HTTPLiveSource::getFormatMeta(bool audio) { ATSParser::SourceType type = audio ? ATSParser::AUDIO : ATSParser::VIDEO; diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h index f22af5b..9950a9e 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -37,7 +37,6 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { virtual status_t feedMoreTSData(); - virtual sp getFormat(bool audio); virtual status_t dequeueAccessUnit(bool audio, sp *accessUnit); virtual status_t getDuration(int64_t *durationUs); @@ -47,6 +46,8 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { protected: virtual ~HTTPLiveSource(); + virtual sp getFormatMeta(bool audio); + private: enum Flags { // Don't log any URLs. diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index daf60f6..a02732b 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -28,9 +28,11 @@ #include "RTSPSource.h" #include "StreamingSource.h" #include "GenericSource.h" +#include "mp4/MP4Source.h" #include "ATSParser.h" +#include // for property_get #include #include #include @@ -43,6 +45,9 @@ #include "avc_utils.h" +#include "ESDS.h" +#include + namespace android { //////////////////////////////////////////////////////////////////////////////// @@ -81,7 +86,14 @@ void NuPlayer::setDriver(const wp &driver) { void NuPlayer::setDataSource(const sp &source) { sp msg = new AMessage(kWhatSetDataSource, id()); - msg->setObject("source", new StreamingSource(source)); + char prop[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.use-mp4source", prop, NULL) + && (!strcmp(prop, "1") || !strcasecmp(prop, "true"))) { + msg->setObject("source", new MP4Source(source)); + } else { + msg->setObject("source", new StreamingSource(source)); + } + msg->post(); } @@ -679,16 +691,16 @@ status_t NuPlayer::instantiateDecoder(bool audio, sp *decoder) { return OK; } - sp meta = mSource->getFormat(audio); + sp format = mSource->getFormat(audio); - if (meta == NULL) { + if (format == NULL) { return -EWOULDBLOCK; } if (!audio) { - const char *mime; - CHECK(meta->findCString(kKeyMIMEType, &mime)); - mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime); + AString mime; + CHECK(format->findString("mime", &mime)); + mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str()); } sp notify = @@ -699,7 +711,7 @@ status_t NuPlayer::instantiateDecoder(bool audio, sp *decoder) { new Decoder(notify, mNativeWindow); looper()->registerHandler(*decoder); - (*decoder)->configure(meta); + (*decoder)->configure(format); int64_t durationUs; if (mDriver != NULL && mSource->getDuration(&durationUs) == OK) { @@ -930,4 +942,19 @@ void NuPlayer::flushDecoder(bool audio, bool needShutdown) { } } +sp NuPlayer::Source::getFormat(bool audio) { + sp meta = getFormatMeta(audio); + + if (meta == NULL) { + return NULL; + } + + sp msg = new AMessage; + + if(convertMetaDataToMessage(meta, &msg) == OK) { + return msg; + } + return NULL; +} + } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index 25766e0..996806e 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -60,14 +60,16 @@ protected: virtual void onMessageReceived(const sp &msg); +public: + struct NuPlayerStreamListener; + struct Source; + private: struct Decoder; struct GenericSource; struct HTTPLiveSource; - struct NuPlayerStreamListener; struct Renderer; struct RTSPSource; - struct Source; struct StreamingSource; enum { diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp index 0e53662..22f699e 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -20,15 +20,11 @@ #include "NuPlayerDecoder.h" -#include "ESDS.h" - #include #include #include #include #include -#include -#include namespace android { @@ -42,16 +38,24 @@ NuPlayer::Decoder::Decoder( NuPlayer::Decoder::~Decoder() { } -void NuPlayer::Decoder::configure(const sp &meta) { +void NuPlayer::Decoder::configure(const sp &format) { CHECK(mCodec == NULL); - const char *mime; - CHECK(meta->findCString(kKeyMIMEType, &mime)); + AString mime; + CHECK(format->findString("mime", &mime)); sp notifyMsg = new AMessage(kWhatCodecNotify, id()); - sp format = makeFormat(meta); + mCSDIndex = 0; + for (size_t i = 0;; ++i) { + sp csd; + if (!format->findBuffer(StringPrintf("csd-%d", i).c_str(), &csd)) { + break; + } + + mCSD.push(csd); + } if (mNativeWindow != NULL) { format->setObject("native-window", mNativeWindow); @@ -61,7 +65,7 @@ void NuPlayer::Decoder::configure(const sp &meta) { // quickly, violating the OpenMAX specs, until that is remedied // we need to invest in an extra looper to free the main event // queue. - bool needDedicatedLooper = !strncasecmp(mime, "video/", 6); + bool needDedicatedLooper = !strncasecmp(mime.c_str(), "video/", 6); mCodec = new ACodec; @@ -100,25 +104,6 @@ void NuPlayer::Decoder::onMessageReceived(const sp &msg) { } } -sp NuPlayer::Decoder::makeFormat(const sp &meta) { - CHECK(mCSD.isEmpty()); - - sp msg; - CHECK_EQ(convertMetaDataToMessage(meta, &msg), (status_t)OK); - - mCSDIndex = 0; - for (size_t i = 0;; ++i) { - sp csd; - if (!msg->findBuffer(StringPrintf("csd-%d", i).c_str(), &csd)) { - break; - } - - mCSD.push(csd); - } - - return msg; -} - void NuPlayer::Decoder::onFillThisBuffer(const sp &msg) { sp reply; CHECK(msg->findMessage("reply", &reply)); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h index 3ab1fcf..a876148 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h @@ -30,7 +30,7 @@ struct NuPlayer::Decoder : public AHandler { Decoder(const sp ¬ify, const sp &nativeWindow = NULL); - void configure(const sp &meta); + void configure(const sp &format); void signalFlush(); void signalResume(); diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h index 531b29f..66aeff3 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h @@ -34,7 +34,7 @@ struct NuPlayer::Source : public RefBase { // an error or ERROR_END_OF_STREAM if not. virtual status_t feedMoreTSData() = 0; - virtual sp getFormat(bool audio) = 0; + virtual sp getFormat(bool audio); virtual status_t dequeueAccessUnit( bool audio, sp *accessUnit) = 0; @@ -54,6 +54,8 @@ struct NuPlayer::Source : public RefBase { protected: virtual ~Source() {} + virtual sp getFormatMeta(bool audio) { return NULL; } + private: DISALLOW_EVIL_CONSTRUCTORS(Source); }; diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp index c910488..4a704e3 100644 --- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp @@ -95,7 +95,7 @@ status_t NuPlayer::RTSPSource::feedMoreTSData() { return mFinalResult; } -sp NuPlayer::RTSPSource::getFormat(bool audio) { +sp NuPlayer::RTSPSource::getFormatMeta(bool audio) { sp source = getSource(audio); if (source == NULL) { diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.h b/media/libmediaplayerservice/nuplayer/RTSPSource.h index e11e304..c8409e5 100644 --- a/media/libmediaplayerservice/nuplayer/RTSPSource.h +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.h @@ -40,7 +40,6 @@ struct NuPlayer::RTSPSource : public NuPlayer::Source { virtual status_t feedMoreTSData(); - virtual sp getFormat(bool audio); virtual status_t dequeueAccessUnit(bool audio, sp *accessUnit); virtual status_t getDuration(int64_t *durationUs); @@ -52,6 +51,8 @@ struct NuPlayer::RTSPSource : public NuPlayer::Source { protected: virtual ~RTSPSource(); + virtual sp getFormatMeta(bool audio); + private: enum { kWhatNotify = 'noti', diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp index 7c9bc5e..b696aa4 100644 --- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp +++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp @@ -107,7 +107,7 @@ status_t NuPlayer::StreamingSource::feedMoreTSData() { return OK; } -sp NuPlayer::StreamingSource::getFormat(bool audio) { +sp NuPlayer::StreamingSource::getFormatMeta(bool audio) { ATSParser::SourceType type = audio ? ATSParser::AUDIO : ATSParser::VIDEO; diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.h b/media/libmediaplayerservice/nuplayer/StreamingSource.h index ca00ef9..3971e2a 100644 --- a/media/libmediaplayerservice/nuplayer/StreamingSource.h +++ b/media/libmediaplayerservice/nuplayer/StreamingSource.h @@ -33,12 +33,13 @@ struct NuPlayer::StreamingSource : public NuPlayer::Source { virtual status_t feedMoreTSData(); - virtual sp getFormat(bool audio); virtual status_t dequeueAccessUnit(bool audio, sp *accessUnit); protected: virtual ~StreamingSource(); + virtual sp getFormatMeta(bool audio); + private: sp mSource; status_t mFinalResult; diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp new file mode 100644 index 0000000..25c91e9 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012 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 "MP4Source.h" + +#include "Parser.h" +#include "../NuPlayerStreamListener.h" + +#include +#include +#include +#include + +namespace android { + +struct StreamSource : public Parser::Source { + StreamSource(const sp &source) + : mListener(new NuPlayer::NuPlayerStreamListener(source, 0)), + mPosition(0) { + mListener->start(); + } + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) { + if (offset < mPosition) { + return -EPIPE; + } + + while (offset > mPosition) { + char buffer[1024]; + off64_t skipBytes = offset - mPosition; + if (skipBytes > sizeof(buffer)) { + skipBytes = sizeof(buffer); + } + + sp extra; + ssize_t n; + for (;;) { + n = mListener->read(buffer, skipBytes, &extra); + + if (n == -EWOULDBLOCK) { + usleep(10000); + continue; + } + + break; + } + + ALOGV("skipped %ld bytes at offset %lld", n, mPosition); + + if (n < 0) { + return n; + } + + mPosition += n; + } + + sp extra; + size_t total = 0; + while (total < size) { + ssize_t n = mListener->read( + (uint8_t *)data + total, size - total, &extra); + + if (n == -EWOULDBLOCK) { + usleep(10000); + continue; + } else if (n == 0) { + break; + } else if (n < 0) { + mPosition += total; + return n; + } + + total += n; + } + + ALOGV("read %ld bytes at offset %lld", n, mPosition); + + mPosition += total; + + return total; + } + +private: + sp mListener; + off64_t mPosition; + + DISALLOW_EVIL_CONSTRUCTORS(StreamSource); +}; + +MP4Source::MP4Source(const sp &source) + : mSource(source), + mLooper(new ALooper), + mParser(new Parser), + mEOS(false) { + mLooper->registerHandler(mParser); +} + +MP4Source::~MP4Source() { +} + +void MP4Source::start() { + mLooper->start(false /* runOnCallingThread */); + mParser->start(new StreamSource(mSource)); +} + +status_t MP4Source::feedMoreTSData() { + return mEOS ? ERROR_END_OF_STREAM : (status_t)OK; +} + +sp MP4Source::getFormat(bool audio) { + return mParser->getFormat(audio); +} + +status_t MP4Source::dequeueAccessUnit( + bool audio, sp *accessUnit) { + return mParser->dequeueAccessUnit(audio, accessUnit); +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h new file mode 100644 index 0000000..57430aa --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 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 MP4_SOURCE_H +#define MP4_SOURCE_H + +#include "NuPlayerSource.h" + +namespace android { + +struct Parser; + +struct MP4Source : public NuPlayer::Source { + MP4Source(const sp &source); + + virtual void start(); + + virtual status_t feedMoreTSData(); + + virtual sp getFormat(bool audio); + + virtual status_t dequeueAccessUnit( + bool audio, sp *accessUnit); + +protected: + virtual ~MP4Source(); + +private: + sp mSource; + sp mLooper; + sp mParser; + bool mEOS; + + DISALLOW_EVIL_CONSTRUCTORS(MP4Source); +}; + +} // namespace android + +#endif // MP4_SOURCE_H diff --git a/media/libmediaplayerservice/nuplayer/mp4/Parser.cpp b/media/libmediaplayerservice/nuplayer/mp4/Parser.cpp new file mode 100644 index 0000000..b9fe819 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/Parser.cpp @@ -0,0 +1,1643 @@ +/* + * Copyright (C) 2012 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 "Parser" +#include + +#include "Parser.h" +#include "TrackFragment.h" + +#include "ESDS.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../NuPlayerStreamListener.h" + +namespace android { + +static const char *Fourcc2String(uint32_t fourcc) { + static char buffer[5]; + buffer[4] = '\0'; + buffer[0] = fourcc >> 24; + buffer[1] = (fourcc >> 16) & 0xff; + buffer[2] = (fourcc >> 8) & 0xff; + buffer[3] = fourcc & 0xff; + + return buffer; +} + +static const char *IndentString(size_t n) { + static const char kSpace[] = " "; + return kSpace + sizeof(kSpace) - 2 * n - 1; +} + +// static +const Parser::DispatchEntry Parser::kDispatchTable[] = { + { FOURCC('m', 'o', 'o', 'v'), 0, NULL }, + { FOURCC('t', 'r', 'a', 'k'), FOURCC('m', 'o', 'o', 'v'), NULL }, + { FOURCC('u', 'd', 't', 'a'), FOURCC('t', 'r', 'a', 'k'), NULL }, + { FOURCC('u', 'd', 't', 'a'), FOURCC('m', 'o', 'o', 'v'), NULL }, + { FOURCC('m', 'e', 't', 'a'), FOURCC('u', 'd', 't', 'a'), NULL }, + { FOURCC('i', 'l', 's', 't'), FOURCC('m', 'e', 't', 'a'), NULL }, + + { FOURCC('t', 'k', 'h', 'd'), FOURCC('t', 'r', 'a', 'k'), + &Parser::parseTrackHeader + }, + + { FOURCC('m', 'v', 'e', 'x'), FOURCC('m', 'o', 'o', 'v'), NULL }, + + { FOURCC('t', 'r', 'e', 'x'), FOURCC('m', 'v', 'e', 'x'), + &Parser::parseTrackExtends + }, + + { FOURCC('e', 'd', 't', 's'), FOURCC('t', 'r', 'a', 'k'), NULL }, + { FOURCC('m', 'd', 'i', 'a'), FOURCC('t', 'r', 'a', 'k'), NULL }, + + { FOURCC('m', 'd', 'h', 'd'), FOURCC('m', 'd', 'i', 'a'), + &Parser::parseMediaHeader + }, + + { FOURCC('h', 'd', 'l', 'r'), FOURCC('m', 'd', 'i', 'a'), + &Parser::parseMediaHandler + }, + + { FOURCC('m', 'i', 'n', 'f'), FOURCC('m', 'd', 'i', 'a'), NULL }, + { FOURCC('d', 'i', 'n', 'f'), FOURCC('m', 'i', 'n', 'f'), NULL }, + { FOURCC('s', 't', 'b', 'l'), FOURCC('m', 'i', 'n', 'f'), NULL }, + { FOURCC('s', 't', 's', 'd'), FOURCC('s', 't', 'b', 'l'), NULL }, + + { FOURCC('s', 't', 's', 'z'), FOURCC('s', 't', 'b', 'l'), + &Parser::parseSampleSizes }, + + { FOURCC('s', 't', 'z', '2'), FOURCC('s', 't', 'b', 'l'), + &Parser::parseCompactSampleSizes }, + + { FOURCC('s', 't', 's', 'c'), FOURCC('s', 't', 'b', 'l'), + &Parser::parseSampleToChunk }, + + { FOURCC('s', 't', 'c', 'o'), FOURCC('s', 't', 'b', 'l'), + &Parser::parseChunkOffsets }, + + { FOURCC('c', 'o', '6', '4'), FOURCC('s', 't', 'b', 'l'), + &Parser::parseChunkOffsets64 }, + + { FOURCC('a', 'v', 'c', 'C'), FOURCC('a', 'v', 'c', '1'), + &Parser::parseAVCCodecSpecificData }, + + { FOURCC('e', 's', 'd', 's'), FOURCC('m', 'p', '4', 'a'), + &Parser::parseESDSCodecSpecificData }, + + { FOURCC('e', 's', 'd', 's'), FOURCC('m', 'p', '4', 'v'), + &Parser::parseESDSCodecSpecificData }, + + { FOURCC('m', 'd', 'a', 't'), 0, &Parser::parseMediaData }, + + { FOURCC('m', 'o', 'o', 'f'), 0, NULL }, + { FOURCC('t', 'r', 'a', 'f'), FOURCC('m', 'o', 'o', 'f'), NULL }, + + { FOURCC('t', 'f', 'h', 'd'), FOURCC('t', 'r', 'a', 'f'), + &Parser::parseTrackFragmentHeader + }, + { FOURCC('t', 'r', 'u', 'n'), FOURCC('t', 'r', 'a', 'f'), + &Parser::parseTrackFragmentRun + }, + + { FOURCC('m', 'f', 'r', 'a'), 0, NULL }, +}; + +struct FileSource : public Parser::Source { + FileSource(const char *filename) + : mFile(fopen(filename, "rb")) { + CHECK(mFile != NULL); + } + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) { + fseek(mFile, offset, SEEK_SET); + return fread(data, 1, size, mFile); + } + + private: + FILE *mFile; + + DISALLOW_EVIL_CONSTRUCTORS(FileSource); +}; + +Parser::Parser() + : mBufferPos(0), + mSuspended(false) { +} + +Parser::~Parser() { +} + +void Parser::start(const char *filename) { + sp msg = new AMessage(kWhatStart, id()); + msg->setObject("source", new FileSource(filename)); + msg->post(); +} + +void Parser::start(const sp &source) { + sp msg = new AMessage(kWhatStart, id()); + msg->setObject("source", source); + msg->post(); +} + +sp Parser::getFormat(bool audio) { + sp msg = new AMessage(kWhatGetFormat, id()); + msg->setInt32("audio", audio); + + sp response; + status_t err = msg->postAndAwaitResponse(&response); + + if (err != OK) { + return NULL; + } + + if (response->findInt32("err", &err) && err != OK) { + return NULL; + } + + sp format; + CHECK(response->findMessage("format", &format)); + + ALOGV("returning format %s", format->debugString().c_str()); + return format; +} + +status_t Parser::dequeueAccessUnit(bool audio, sp *accessUnit) { + sp msg = new AMessage(kWhatDequeueAccessUnit, id()); + msg->setInt32("audio", audio); + + sp response; + status_t err = msg->postAndAwaitResponse(&response); + + if (err != OK) { + return err; + } + + if (response->findInt32("err", &err) && err != OK) { + return err; + } + + CHECK(response->findBuffer("accessUnit", accessUnit)); + + return OK; +} + +ssize_t Parser::findTrack(bool wantAudio) const { + for (size_t i = 0; i < mTracks.size(); ++i) { + const TrackInfo *info = &mTracks.valueAt(i); + + bool isAudio = + info->mMediaHandlerType == FOURCC('s', 'o', 'u', 'n'); + + bool isVideo = + info->mMediaHandlerType == FOURCC('v', 'i', 'd', 'e'); + + if ((wantAudio && isAudio) || (!wantAudio && !isAudio)) { + if (info->mSampleDescs.empty()) { + break; + } + + return i; + } + } + + return -EWOULDBLOCK; +} + +void Parser::onMessageReceived(const sp &msg) { + switch (msg->what()) { + case kWhatStart: + { + sp obj; + CHECK(msg->findObject("source", &obj)); + + mSource = static_cast(obj.get()); + + mBuffer = new ABuffer(512 * 1024); + mBuffer->setRange(0, 0); + + enter(0, 0); + + (new AMessage(kWhatProceed, id()))->post(); + break; + } + + case kWhatProceed: + { + CHECK(!mSuspended); + + status_t err = onProceed(); + + if (err == OK) { + if (!mSuspended) { + msg->post(); + } + } else if (err != -EAGAIN) { + ALOGE("onProceed returned error %d", err); + } + + break; + } + + case kWhatReadMore: + { + size_t needed; + CHECK(msg->findSize("needed", &needed)); + + memmove(mBuffer->base(), mBuffer->data(), mBuffer->size()); + mBufferPos += mBuffer->offset(); + mBuffer->setRange(0, mBuffer->size()); + + size_t maxBytesToRead = mBuffer->capacity() - mBuffer->size(); + CHECK_GE(maxBytesToRead, needed); + + ssize_t n = mSource->readAt( + mBufferPos + mBuffer->size(), + mBuffer->data() + mBuffer->size(), needed); + + if (n < (ssize_t)needed) { + ALOGI("%s", "Reached EOF"); + } else { + mBuffer->setRange(0, mBuffer->size() + n); + (new AMessage(kWhatProceed, id()))->post(); + } + + break; + } + + case kWhatGetFormat: + { + int32_t wantAudio; + CHECK(msg->findInt32("audio", &wantAudio)); + + status_t err = -EWOULDBLOCK; + sp response = new AMessage; + + ssize_t trackIndex = findTrack(wantAudio); + + if (trackIndex < 0) { + err = trackIndex; + } else { + TrackInfo *info = &mTracks.editValueAt(trackIndex); + + response->setMessage( + "format", info->mSampleDescs.itemAt(0).mFormat); + + err = OK; + } + + response->setInt32("err", err); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + response->postReply(replyID); + break; + } + + case kWhatDequeueAccessUnit: + { + int32_t wantAudio; + CHECK(msg->findInt32("audio", &wantAudio)); + + status_t err = -EWOULDBLOCK; + sp response = new AMessage; + + ssize_t trackIndex = findTrack(wantAudio); + + if (trackIndex < 0) { + err = trackIndex; + } else { + sp accessUnit; + err = onDequeueAccessUnit(trackIndex, &accessUnit); + + if (err == OK) { + response->setBuffer("accessUnit", accessUnit); + } + } + + response->setInt32("err", err); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + response->postReply(replyID); + break; + } + + default: + TRESPASS(); + } +} + +status_t Parser::onProceed() { + status_t err; + + if ((err = need(8)) != OK) { + return err; + } + + uint64_t size = readU32(0); + uint32_t type = readU32(4); + + size_t offset = 8; + + if (size == 1) { + if ((err = need(16)) != OK) { + return err; + } + + size = readU64(offset); + offset += 8; + } + + uint8_t userType[16]; + + if (type == FOURCC('u', 'u', 'i', 'd')) { + if ((err = need(offset + 16)) != OK) { + return err; + } + + memcpy(userType, mBuffer->data() + offset, 16); + offset += 16; + } + + CHECK(!mStack.isEmpty()); + uint32_t ptype = mStack.itemAt(mStack.size() - 1).mType; + + static const size_t kNumDispatchers = + sizeof(kDispatchTable) / sizeof(kDispatchTable[0]); + + size_t i; + for (i = 0; i < kNumDispatchers; ++i) { + if (kDispatchTable[i].mType == type + && kDispatchTable[i].mParentType == ptype) { + break; + } + } + + // SampleEntry boxes are container boxes that start with a variable + // amount of data depending on the media handler type. + // We don't look inside 'hint' type SampleEntry boxes. + + bool isSampleEntryBox = + (ptype == FOURCC('s', 't', 's', 'd')) + && editTrack(mCurrentTrackID)->mMediaHandlerType + != FOURCC('h', 'i', 'n', 't'); + + if ((i < kNumDispatchers && kDispatchTable[i].mHandler == 0) + || isSampleEntryBox || ptype == FOURCC('i', 'l', 's', 't')) { + // This is a container box. + if (type == FOURCC('m', 'e', 't', 'a')) { + if ((err = need(offset + 4)) < OK) { + return err; + } + + if (readU32(offset) != 0) { + return -EINVAL; + } + + offset += 4; + } else if (type == FOURCC('s', 't', 's', 'd')) { + if ((err = need(offset + 8)) < OK) { + return err; + } + + if (readU32(offset) != 0) { + return -EINVAL; + } + + if (readU32(offset + 4) == 0) { + // We need at least some entries. + return -EINVAL; + } + + offset += 8; + } else if (isSampleEntryBox) { + size_t headerSize; + + switch (editTrack(mCurrentTrackID)->mMediaHandlerType) { + case FOURCC('v', 'i', 'd', 'e'): + { + // 8 bytes SampleEntry + 70 bytes VisualSampleEntry + headerSize = 78; + break; + } + + case FOURCC('s', 'o', 'u', 'n'): + { + // 8 bytes SampleEntry + 20 bytes AudioSampleEntry + headerSize = 28; + break; + } + + case FOURCC('m', 'e', 't', 'a'): + { + headerSize = 8; // 8 bytes SampleEntry + break; + } + + default: + TRESPASS(); + } + + if (offset + headerSize > size) { + return -EINVAL; + } + + if ((err = need(offset + headerSize)) != OK) { + return err; + } + + switch (editTrack(mCurrentTrackID)->mMediaHandlerType) { + case FOURCC('v', 'i', 'd', 'e'): + { + err = parseVisualSampleEntry( + type, offset, offset + headerSize); + break; + } + + case FOURCC('s', 'o', 'u', 'n'): + { + err = parseAudioSampleEntry( + type, offset, offset + headerSize); + break; + } + + case FOURCC('m', 'e', 't', 'a'): + { + err = OK; + break; + } + + default: + TRESPASS(); + } + + if (err != OK) { + return err; + } + + offset += headerSize; + } + + skip(offset); + + ALOGV("%sentering box of type '%s'", + IndentString(mStack.size()), Fourcc2String(type)); + + enter(type, size - offset); + } else { + if (!fitsContainer(size)) { + return -EINVAL; + } + + if (i < kNumDispatchers && kDispatchTable[i].mHandler != 0) { + // We have a handler for this box type. + + if ((err = need(size)) != OK) { + return err; + } + + ALOGV("%sparsing box of type '%s'", + IndentString(mStack.size()), Fourcc2String(type)); + + if ((err = (this->*kDispatchTable[i].mHandler)( + type, offset, size)) != OK) { + return err; + } + } else { + // Unknown box type + + ALOGV("%sskipping box of type '%s', size %llu", + IndentString(mStack.size()), + Fourcc2String(type), size); + + } + + skip(size); + } + + return OK; +} + +// static +int Parser::CompareSampleLocation( + const SampleInfo &sample, const MediaDataInfo &mdatInfo) { + if (sample.mOffset + sample.mSize < mdatInfo.mOffset) { + return -1; + } + + if (sample.mOffset >= mdatInfo.mOffset + mdatInfo.mBuffer->size()) { + return 1; + } + + // Otherwise make sure the sample is completely contained within this + // media data block. + + CHECK_GE(sample.mOffset, mdatInfo.mOffset); + + CHECK_LE(sample.mOffset + sample.mSize, + mdatInfo.mOffset + mdatInfo.mBuffer->size()); + + return 0; +} + +void Parser::resumeIfNecessary() { + if (!mSuspended) { + return; + } + + ALOGI("resuming."); + + mSuspended = false; + (new AMessage(kWhatProceed, id()))->post(); +} + +status_t Parser::getSample( + TrackInfo *info, sp *fragment, SampleInfo *sampleInfo) { + for (;;) { + if (info->mFragments.empty()) { + resumeIfNecessary(); + return -EWOULDBLOCK; + } + + *fragment = *info->mFragments.begin(); + + status_t err = (*fragment)->getSample(sampleInfo); + + if (err == OK) { + return OK; + } else if (err != ERROR_END_OF_STREAM) { + return err; + } + + // Really, end of this fragment... + + info->mFragments.erase(info->mFragments.begin()); + } +} + +status_t Parser::onDequeueAccessUnit( + size_t trackIndex, sp *accessUnit) { + TrackInfo *info = &mTracks.editValueAt(trackIndex); + + sp fragment; + SampleInfo sampleInfo; + status_t err = getSample(info, &fragment, &sampleInfo); + + if (err == -EWOULDBLOCK) { + resumeIfNecessary(); + return err; + } else if (err != OK) { + return err; + } + + err = -EWOULDBLOCK; + + bool checkDroppable = false; + + for (size_t i = 0; i < mMediaData.size(); ++i) { + const MediaDataInfo &mdatInfo = mMediaData.itemAt(i); + + int cmp = CompareSampleLocation(sampleInfo, mdatInfo); + + if (cmp < 0) { + return -EPIPE; + } else if (cmp == 0) { + if (i > 0) { + checkDroppable = true; + } + + err = makeAccessUnit(info, sampleInfo, mdatInfo, accessUnit); + break; + } + } + + if (err != OK) { + return err; + } + + fragment->advance(); + + if (!mMediaData.empty() && checkDroppable) { + size_t numDroppable = 0; + bool done = false; + + for (size_t i = 0; !done && i < mMediaData.size(); ++i) { + const MediaDataInfo &mdatInfo = mMediaData.itemAt(i); + + for (size_t j = 0; j < mTracks.size(); ++j) { + TrackInfo *info = &mTracks.editValueAt(j); + + sp fragment; + SampleInfo sampleInfo; + err = getSample(info, &fragment, &sampleInfo); + + if (err != OK) { + done = true; + break; + } + + int cmp = CompareSampleLocation(sampleInfo, mdatInfo); + + if (cmp <= 0) { + done = true; + break; + } + } + + if (!done) { + ++numDroppable; + } + } + + if (numDroppable > 0) { + mMediaData.removeItemsAt(0, numDroppable); + + if (mMediaData.size() < 5) { + resumeIfNecessary(); + } + } + } + + return err; +} + +static size_t parseNALSize(size_t nalLengthSize, const uint8_t *data) { + switch (nalLengthSize) { + case 1: + return *data; + case 2: + return U16_AT(data); + case 3: + return ((size_t)data[0] << 16) | U16_AT(&data[1]); + case 4: + return U32_AT(data); + } + + // This cannot happen, mNALLengthSize springs to life by adding 1 to + // a 2-bit integer. + TRESPASS(); + + return 0; +} + +status_t Parser::makeAccessUnit( + TrackInfo *info, + const SampleInfo &sample, + const MediaDataInfo &mdatInfo, + sp *accessUnit) { + if (sample.mSampleDescIndex < 1 + || sample.mSampleDescIndex > info->mSampleDescs.size()) { + return ERROR_MALFORMED; + } + + int64_t presentationTimeUs = + 1000000ll * sample.mPresentationTime / info->mMediaTimeScale; + + const SampleDescription &sampleDesc = + info->mSampleDescs.itemAt(sample.mSampleDescIndex - 1); + + size_t nalLengthSize; + if (!sampleDesc.mFormat->findSize("nal-length-size", &nalLengthSize)) { + *accessUnit = new ABuffer(sample.mSize); + + memcpy((*accessUnit)->data(), + mdatInfo.mBuffer->data() + (sample.mOffset - mdatInfo.mOffset), + sample.mSize); + + (*accessUnit)->meta()->setInt64("timeUs", presentationTimeUs); + return OK; + } + + const uint8_t *srcPtr = + mdatInfo.mBuffer->data() + (sample.mOffset - mdatInfo.mOffset); + + for (int i = 0; i < 2 ; ++i) { + size_t srcOffset = 0; + size_t dstOffset = 0; + + while (srcOffset < sample.mSize) { + if (srcOffset + nalLengthSize > sample.mSize) { + return ERROR_MALFORMED; + } + + size_t nalSize = parseNALSize(nalLengthSize, &srcPtr[srcOffset]); + srcOffset += nalLengthSize; + + if (srcOffset + nalSize > sample.mSize) { + return ERROR_MALFORMED; + } + + if (i == 1) { + memcpy((*accessUnit)->data() + dstOffset, + "\x00\x00\x00\x01", + 4); + + memcpy((*accessUnit)->data() + dstOffset + 4, + srcPtr + srcOffset, + nalSize); + } + + srcOffset += nalSize; + dstOffset += nalSize + 4; + } + + if (i == 0) { + (*accessUnit) = new ABuffer(dstOffset); + (*accessUnit)->meta()->setInt64( + "timeUs", presentationTimeUs); + } + } + + return OK; +} + +status_t Parser::need(size_t size) { + if (!fitsContainer(size)) { + return -EINVAL; + } + + if (size <= mBuffer->size()) { + return OK; + } + + sp msg = new AMessage(kWhatReadMore, id()); + msg->setSize("needed", size - mBuffer->size()); + msg->post(); + + // ALOGV("need(%d) returning -EAGAIN, only have %d", size, mBuffer->size()); + + return -EAGAIN; +} + +void Parser::enter(uint32_t type, uint64_t size) { + Container container; + container.mType = type; + container.mExtendsToEOF = (size == 0); + container.mBytesRemaining = size; + + mStack.push(container); +} + +bool Parser::fitsContainer(uint64_t size) const { + CHECK(!mStack.isEmpty()); + const Container &container = mStack.itemAt(mStack.size() - 1); + + return container.mExtendsToEOF || size <= container.mBytesRemaining; +} + +uint16_t Parser::readU16(size_t offset) { + CHECK_LE(offset + 2, mBuffer->size()); + + const uint8_t *ptr = mBuffer->data() + offset; + return (ptr[0] << 8) | ptr[1]; +} + +uint32_t Parser::readU32(size_t offset) { + CHECK_LE(offset + 4, mBuffer->size()); + + const uint8_t *ptr = mBuffer->data() + offset; + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +uint64_t Parser::readU64(size_t offset) { + return (((uint64_t)readU32(offset)) << 32) | readU32(offset + 4); +} + +void Parser::skip(off_t distance) { + CHECK(!mStack.isEmpty()); + for (size_t i = mStack.size(); i-- > 0;) { + Container *container = &mStack.editItemAt(i); + if (!container->mExtendsToEOF) { + CHECK_LE(distance, (off_t)container->mBytesRemaining); + + container->mBytesRemaining -= distance; + + if (container->mBytesRemaining == 0) { + ALOGV("%sleaving box of type '%s'", + IndentString(mStack.size() - 1), + Fourcc2String(container->mType)); + +#if 0 + if (container->mType == FOURCC('s', 't', 's', 'd')) { + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + for (size_t i = 0; + i < trackInfo->mSampleDescs.size(); ++i) { + ALOGI("format #%d: %s", + i, + trackInfo->mSampleDescs.itemAt(i) + .mFormat->debugString().c_str()); + } + } +#endif + + if (container->mType == FOURCC('s', 't', 'b', 'l')) { + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + + trackInfo->mStaticFragment->signalCompletion(); + + CHECK(trackInfo->mFragments.empty()); + trackInfo->mFragments.push_back(trackInfo->mStaticFragment); + trackInfo->mStaticFragment.clear(); + } else if (container->mType == FOURCC('t', 'r', 'a', 'f')) { + TrackInfo *trackInfo = + editTrack(mTrackFragmentHeaderInfo.mTrackID); + + const sp &fragment = + *--trackInfo->mFragments.end(); + + static_cast( + fragment.get())->signalCompletion(); + } + + container = NULL; + mStack.removeItemsAt(i); + } + } + } + + if (distance < (off_t)mBuffer->size()) { + mBuffer->setRange(mBuffer->offset() + distance, mBuffer->size() - distance); + mBufferPos += distance; + return; + } + + mBuffer->setRange(0, 0); + mBufferPos += distance; +} + +status_t Parser::parseTrackHeader( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 4 > size) { + return -EINVAL; + } + + uint32_t flags = readU32(offset); + + uint32_t version = flags >> 24; + flags &= 0xffffff; + + uint32_t trackID; + uint64_t duration; + + if (version == 1) { + if (offset + 36 > size) { + return -EINVAL; + } + + trackID = readU32(offset + 20); + duration = readU64(offset + 28); + + offset += 36; + } else if (version == 0) { + if (offset + 24 > size) { + return -EINVAL; + } + + trackID = readU32(offset + 12); + duration = readU32(offset + 20); + + offset += 24; + } else { + return -EINVAL; + } + + TrackInfo *info = editTrack(trackID, true /* createIfNecessary */); + info->mFlags = flags; + info->mDuration = duration; + + info->mStaticFragment = new StaticTrackFragment; + + mCurrentTrackID = trackID; + + return OK; +} + +status_t Parser::parseMediaHeader( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 4 > size) { + return -EINVAL; + } + + uint32_t versionAndFlags = readU32(offset); + + if (versionAndFlags & 0xffffff) { + return ERROR_MALFORMED; + } + + uint32_t version = versionAndFlags >> 24; + + TrackInfo *info = editTrack(mCurrentTrackID); + + if (version == 1) { + if (offset + 4 + 32 > size) { + return -EINVAL; + } + info->mMediaTimeScale = U32_AT(mBuffer->data() + offset + 20); + } else if (version == 0) { + if (offset + 4 + 20 > size) { + return -EINVAL; + } + info->mMediaTimeScale = U32_AT(mBuffer->data() + offset + 12); + } else { + return ERROR_MALFORMED; + } + + return OK; +} + +status_t Parser::parseMediaHandler( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 12 > size) { + return -EINVAL; + } + + if (readU32(offset) != 0) { + return -EINVAL; + } + + uint32_t handlerType = readU32(offset + 8); + + switch (handlerType) { + case FOURCC('v', 'i', 'd', 'e'): + case FOURCC('s', 'o', 'u', 'n'): + case FOURCC('h', 'i', 'n', 't'): + case FOURCC('m', 'e', 't', 'a'): + break; + + default: + return -EINVAL; + } + + editTrack(mCurrentTrackID)->mMediaHandlerType = handlerType; + + return OK; +} + +status_t Parser::parseVisualSampleEntry( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 78 > size) { + return -EINVAL; + } + + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + + trackInfo->mSampleDescs.push(); + SampleDescription *sampleDesc = + &trackInfo->mSampleDescs.editItemAt( + trackInfo->mSampleDescs.size() - 1); + + sampleDesc->mType = type; + sampleDesc->mDataRefIndex = readU16(offset + 6); + + sp format = new AMessage; + + switch (type) { + case FOURCC('a', 'v', 'c', '1'): + format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC); + break; + case FOURCC('m', 'p', '4', 'v'): + format->setString("mime", MEDIA_MIMETYPE_VIDEO_MPEG4); + break; + default: + format->setString("mime", "application/octet-stream"); + break; + } + + format->setInt32("width", readU16(offset + 8 + 16)); + format->setInt32("height", readU16(offset + 8 + 18)); + + sampleDesc->mFormat = format; + + return OK; +} + +status_t Parser::parseAudioSampleEntry( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 28 > size) { + return -EINVAL; + } + + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + + trackInfo->mSampleDescs.push(); + SampleDescription *sampleDesc = + &trackInfo->mSampleDescs.editItemAt( + trackInfo->mSampleDescs.size() - 1); + + sampleDesc->mType = type; + sampleDesc->mDataRefIndex = readU16(offset + 6); + + sp format = new AMessage; + + format->setInt32("channel-count", readU16(offset + 8 + 8)); + format->setInt32("sample-size", readU16(offset + 8 + 10)); + format->setInt32("sample-rate", readU32(offset + 8 + 16) / 65536.0f); + + switch (type) { + case FOURCC('m', 'p', '4', 'a'): + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC); + break; + case FOURCC('s', 'a', 'm', 'r'): + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_NB); + format->setInt32("channel-count", 1); + format->setInt32("sample-rate", 8000); + break; + case FOURCC('s', 'a', 'w', 'b'): + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_WB); + format->setInt32("channel-count", 1); + format->setInt32("sample-rate", 16000); + break; + default: + format->setString("mime", "application/octet-stream"); + break; + } + + sampleDesc->mFormat = format; + + return OK; +} + +static void addCodecSpecificData( + const sp &format, int32_t index, + const void *data, size_t size, + bool insertStartCode = false) { + sp csd = new ABuffer(insertStartCode ? size + 4 : size); + + memcpy(csd->data() + (insertStartCode ? 4 : 0), data, size); + + if (insertStartCode) { + memcpy(csd->data(), "\x00\x00\x00\x01", 4); + } + + csd->meta()->setInt32("csd", true); + csd->meta()->setInt64("timeUs", 0ll); + + format->setBuffer(StringPrintf("csd-%d", index).c_str(), csd); +} + +status_t Parser::parseSampleSizes( + uint32_t type, size_t offset, uint64_t size) { + return editTrack(mCurrentTrackID)->mStaticFragment->parseSampleSizes( + this, type, offset, size); +} + +status_t Parser::parseCompactSampleSizes( + uint32_t type, size_t offset, uint64_t size) { + return editTrack(mCurrentTrackID)->mStaticFragment->parseCompactSampleSizes( + this, type, offset, size); +} + +status_t Parser::parseSampleToChunk( + uint32_t type, size_t offset, uint64_t size) { + return editTrack(mCurrentTrackID)->mStaticFragment->parseSampleToChunk( + this, type, offset, size); +} + +status_t Parser::parseChunkOffsets( + uint32_t type, size_t offset, uint64_t size) { + return editTrack(mCurrentTrackID)->mStaticFragment->parseChunkOffsets( + this, type, offset, size); +} + +status_t Parser::parseChunkOffsets64( + uint32_t type, size_t offset, uint64_t size) { + return editTrack(mCurrentTrackID)->mStaticFragment->parseChunkOffsets64( + this, type, offset, size); +} + +status_t Parser::parseAVCCodecSpecificData( + uint32_t type, size_t offset, uint64_t size) { + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + + SampleDescription *sampleDesc = + &trackInfo->mSampleDescs.editItemAt( + trackInfo->mSampleDescs.size() - 1); + + if (sampleDesc->mType != FOURCC('a', 'v', 'c', '1')) { + return -EINVAL; + } + + const uint8_t *ptr = mBuffer->data() + offset; + + size -= offset; + offset = 0; + + if (size < 7 || ptr[0] != 0x01) { + return ERROR_MALFORMED; + } + + sampleDesc->mFormat->setSize("nal-length-size", 1 + (ptr[4] & 3)); + + size_t numSPS = ptr[5] & 31; + + ptr += 6; + size -= 6; + + for (size_t i = 0; i < numSPS; ++i) { + if (size < 2) { + return ERROR_MALFORMED; + } + + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + if (size < length) { + return ERROR_MALFORMED; + } + + addCodecSpecificData( + sampleDesc->mFormat, i, ptr, length, + true /* insertStartCode */); + + ptr += length; + size -= length; + } + + if (size < 1) { + return ERROR_MALFORMED; + } + + size_t numPPS = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPPS; ++i) { + if (size < 2) { + return ERROR_MALFORMED; + } + + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + if (size < length) { + return ERROR_MALFORMED; + } + + addCodecSpecificData( + sampleDesc->mFormat, numSPS + i, ptr, length, + true /* insertStartCode */); + + ptr += length; + size -= length; + } + + return OK; +} + +status_t Parser::parseESDSCodecSpecificData( + uint32_t type, size_t offset, uint64_t size) { + TrackInfo *trackInfo = editTrack(mCurrentTrackID); + + SampleDescription *sampleDesc = + &trackInfo->mSampleDescs.editItemAt( + trackInfo->mSampleDescs.size() - 1); + + if (sampleDesc->mType != FOURCC('m', 'p', '4', 'a') + && sampleDesc->mType != FOURCC('m', 'p', '4', 'v')) { + return -EINVAL; + } + + const uint8_t *ptr = mBuffer->data() + offset; + + size -= offset; + offset = 0; + + if (size < 4) { + return -EINVAL; + } + + if (U32_AT(ptr) != 0) { + return -EINVAL; + } + + ptr += 4; + size -=4; + + ESDS esds(ptr, size); + + uint8_t objectTypeIndication; + if (esds.getObjectTypeIndication(&objectTypeIndication) != OK) { + return ERROR_MALFORMED; + } + + const uint8_t *csd; + size_t csd_size; + if (esds.getCodecSpecificInfo( + (const void **)&csd, &csd_size) != OK) { + return ERROR_MALFORMED; + } + + addCodecSpecificData(sampleDesc->mFormat, 0, csd, csd_size); + + if (sampleDesc->mType != FOURCC('m', 'p', '4', 'a')) { + return OK; + } + + if (csd_size == 0) { + // There's no further information, i.e. no codec specific data + // Let's assume that the information provided in the mpeg4 headers + // is accurate and hope for the best. + + return OK; + } + + if (csd_size < 2) { + return ERROR_MALFORMED; + } + + uint32_t objectType = csd[0] >> 3; + + if (objectType == 31) { + return ERROR_UNSUPPORTED; + } + + uint32_t freqIndex = (csd[0] & 7) << 1 | (csd[1] >> 7); + int32_t sampleRate = 0; + int32_t numChannels = 0; + if (freqIndex == 15) { + if (csd_size < 5) { + return ERROR_MALFORMED; + } + + sampleRate = (csd[1] & 0x7f) << 17 + | csd[2] << 9 + | csd[3] << 1 + | (csd[4] >> 7); + + numChannels = (csd[4] >> 3) & 15; + } else { + static uint32_t kSamplingRate[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350 + }; + + if (freqIndex == 13 || freqIndex == 14) { + return ERROR_MALFORMED; + } + + sampleRate = kSamplingRate[freqIndex]; + numChannels = (csd[1] >> 3) & 15; + } + + if (numChannels == 0) { + return ERROR_UNSUPPORTED; + } + + sampleDesc->mFormat->setInt32("sample-rate", sampleRate); + sampleDesc->mFormat->setInt32("channel-count", numChannels); + + return OK; +} + +status_t Parser::parseMediaData( + uint32_t type, size_t offset, uint64_t size) { + ALOGV("skipping 'mdat' chunk at offsets 0x%08lx-0x%08llx.", + mBufferPos + offset, mBufferPos + size); + + sp buffer = new ABuffer(size - offset); + memcpy(buffer->data(), mBuffer->data() + offset, size - offset); + + mMediaData.push(); + MediaDataInfo *info = &mMediaData.editItemAt(mMediaData.size() - 1); + info->mBuffer = buffer; + info->mOffset = mBufferPos + offset; + + if (mMediaData.size() > 10) { + ALOGI("suspending for now."); + mSuspended = true; + } + + return OK; +} + +status_t Parser::parseTrackExtends( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 24 > size) { + return -EINVAL; + } + + if (readU32(offset) != 0) { + return -EINVAL; + } + + uint32_t trackID = readU32(offset + 4); + + TrackInfo *info = editTrack(trackID, true /* createIfNecessary */); + info->mDefaultSampleDescriptionIndex = readU32(offset + 8); + info->mDefaultSampleDuration = readU32(offset + 12); + info->mDefaultSampleSize = readU32(offset + 16); + info->mDefaultSampleFlags = readU32(offset + 20); + + return OK; +} + +Parser::TrackInfo *Parser::editTrack( + uint32_t trackID, bool createIfNecessary) { + ssize_t i = mTracks.indexOfKey(trackID); + + if (i >= 0) { + return &mTracks.editValueAt(i); + } + + if (!createIfNecessary) { + return NULL; + } + + TrackInfo info; + info.mTrackID = trackID; + info.mFlags = 0; + info.mDuration = 0xffffffff; + info.mMediaTimeScale = 0; + info.mMediaHandlerType = 0; + info.mDefaultSampleDescriptionIndex = 0; + info.mDefaultSampleDuration = 0; + info.mDefaultSampleSize = 0; + info.mDefaultSampleFlags = 0; + + info.mDecodingTime = 0; + + mTracks.add(trackID, info); + return &mTracks.editValueAt(mTracks.indexOfKey(trackID)); +} + +status_t Parser::parseTrackFragmentHeader( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 8 > size) { + return -EINVAL; + } + + uint32_t flags = readU32(offset); + + if (flags & 0xff000000) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mFlags = flags; + + mTrackFragmentHeaderInfo.mTrackID = readU32(offset + 4); + offset += 8; + + if (flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent) { + if (offset + 8 > size) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mBaseDataOffset = readU64(offset); + offset += 8; + } + + if (flags & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mSampleDescriptionIndex = readU32(offset); + offset += 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mDefaultSampleDuration = readU32(offset); + offset += 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mDefaultSampleSize = readU32(offset); + offset += 4; + } + + if (flags & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + mTrackFragmentHeaderInfo.mDefaultSampleFlags = readU32(offset); + offset += 4; + } + + if (!(flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent)) { + CHECK(!mStack.isEmpty()); + + // This should point to the start of the data inside the 'mdat' box + // following the current 'moof' box. + + mTrackFragmentHeaderInfo.mBaseDataOffset = + mBufferPos + mStack.itemAt(mStack.size() - 1).mBytesRemaining + 8; + } + + mTrackFragmentHeaderInfo.mDataOffset = + mTrackFragmentHeaderInfo.mBaseDataOffset; + + TrackInfo *trackInfo = editTrack(mTrackFragmentHeaderInfo.mTrackID); + + if (trackInfo->mFragments.empty() + || (*trackInfo->mFragments.begin())->complete()) { + trackInfo->mFragments.push_back(new DynamicTrackFragment); + } + + return OK; +} + +status_t Parser::parseTrackFragmentRun( + uint32_t type, size_t offset, uint64_t size) { + if (offset + 8 > size) { + return -EINVAL; + } + + enum { + kDataOffsetPresent = 0x01, + kFirstSampleFlagsPresent = 0x04, + kSampleDurationPresent = 0x100, + kSampleSizePresent = 0x200, + kSampleFlagsPresent = 0x400, + kSampleCompositionTimeOffsetPresent = 0x800, + }; + + uint32_t flags = readU32(offset); + + if (flags & 0xff000000) { + return -EINVAL; + } + + if ((flags & kFirstSampleFlagsPresent) && (flags & kSampleFlagsPresent)) { + // These two shall not be used together. + return -EINVAL; + } + + uint32_t sampleCount = readU32(offset + 4); + offset += 8; + + uint64_t dataOffset = mTrackFragmentHeaderInfo.mDataOffset; + + uint32_t firstSampleFlags = 0; + + if (flags & kDataOffsetPresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + int32_t dataOffsetDelta = (int32_t)readU32(offset); + + dataOffset = mTrackFragmentHeaderInfo.mBaseDataOffset + dataOffsetDelta; + + offset += 4; + } + + if (flags & kFirstSampleFlagsPresent) { + if (offset + 4 > size) { + return -EINVAL; + } + + firstSampleFlags = readU32(offset); + offset += 4; + } + + TrackInfo *info = editTrack(mTrackFragmentHeaderInfo.mTrackID); + + if (info == NULL) { + return -EINVAL; + } + + uint32_t sampleDuration = 0, sampleSize = 0, sampleFlags = 0, + sampleCtsOffset = 0; + + size_t bytesPerSample = 0; + if (flags & kSampleDurationPresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) { + sampleDuration = mTrackFragmentHeaderInfo.mDefaultSampleDuration; + } else { + sampleDuration = info->mDefaultSampleDuration; + } + + if (flags & kSampleSizePresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) { + sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize; + } else { + sampleSize = info->mDefaultSampleSize; + } + + if (flags & kSampleFlagsPresent) { + bytesPerSample += 4; + } else if (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) { + sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags; + } else { + sampleFlags = info->mDefaultSampleFlags; + } + + if (flags & kSampleCompositionTimeOffsetPresent) { + bytesPerSample += 4; + } else { + sampleCtsOffset = 0; + } + + if (offset + sampleCount * bytesPerSample > size) { + return -EINVAL; + } + + uint32_t sampleDescIndex = + (mTrackFragmentHeaderInfo.mFlags + & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent) + ? mTrackFragmentHeaderInfo.mSampleDescriptionIndex + : info->mDefaultSampleDescriptionIndex; + + for (uint32_t i = 0; i < sampleCount; ++i) { + if (flags & kSampleDurationPresent) { + sampleDuration = readU32(offset); + offset += 4; + } + + if (flags & kSampleSizePresent) { + sampleSize = readU32(offset); + offset += 4; + } + + if (flags & kSampleFlagsPresent) { + sampleFlags = readU32(offset); + offset += 4; + } + + if (flags & kSampleCompositionTimeOffsetPresent) { + sampleCtsOffset = readU32(offset); + offset += 4; + } + + ALOGV("adding sample at offset 0x%08llx, size %u, duration %u, " + "sampleDescIndex=%u, flags 0x%08x", + dataOffset, sampleSize, sampleDuration, + sampleDescIndex, + (flags & kFirstSampleFlagsPresent) && i == 0 + ? firstSampleFlags : sampleFlags); + + const sp &fragment = *--info->mFragments.end(); + + uint32_t decodingTime = info->mDecodingTime; + info->mDecodingTime += sampleDuration; + uint32_t presentationTime = decodingTime + sampleCtsOffset; + + static_cast( + fragment.get())->addSample( + dataOffset, + sampleSize, + presentationTime, + sampleDescIndex, + ((flags & kFirstSampleFlagsPresent) && i == 0) + ? firstSampleFlags : sampleFlags); + + dataOffset += sampleSize; + } + + mTrackFragmentHeaderInfo.mDataOffset = dataOffset; + + return OK; +} + +void Parser::copyBuffer( + sp *dst, size_t offset, uint64_t size, size_t extra) const { + sp buf = new ABuffer(size + extra); + memcpy(buf->data(), mBuffer->data() + offset, size); + + *dst = buf; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/mp4/Parser.h b/media/libmediaplayerservice/nuplayer/mp4/Parser.h new file mode 100644 index 0000000..50c96bb --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/Parser.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2012 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 PARSER_H_ + +#define PARSER_H_ + +#include +#include + +namespace android { + +struct ABuffer; + +struct Parser : public AHandler { + struct Source : public RefBase { + Source() {} + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) = 0; + + protected: + virtual ~Source() {} + + private: + DISALLOW_EVIL_CONSTRUCTORS(Source); + }; + + Parser(); + + void start(const char *filename); + void start(const sp &source); + + sp getFormat(bool audio); + status_t dequeueAccessUnit(bool audio, sp *accessUnit); + + virtual void onMessageReceived(const sp &msg); + +protected: + virtual ~Parser(); + +private: + enum { + kWhatStart, + kWhatProceed, + kWhatReadMore, + kWhatGetFormat, + kWhatDequeueAccessUnit, + }; + + struct TrackFragment; + struct DynamicTrackFragment; + struct StaticTrackFragment; + + struct DispatchEntry { + uint32_t mType; + uint32_t mParentType; + status_t (Parser::*mHandler)(uint32_t, size_t, uint64_t); + }; + + struct Container { + uint64_t mBytesRemaining; + uint32_t mType; + bool mExtendsToEOF; + }; + + struct SampleDescription { + uint32_t mType; + uint16_t mDataRefIndex; + + sp mFormat; + }; + + struct SampleInfo { + off64_t mOffset; + size_t mSize; + uint32_t mPresentationTime; + size_t mSampleDescIndex; + uint32_t mFlags; + }; + + struct MediaDataInfo { + sp mBuffer; + off64_t mOffset; + }; + + struct TrackInfo { + enum Flags { + kTrackEnabled = 0x01, + kTrackInMovie = 0x02, + kTrackInPreview = 0x04, + }; + + uint32_t mTrackID; + uint32_t mFlags; + uint32_t mDuration; // This is the duration in terms of movie timescale! + + uint32_t mMediaTimeScale; + + uint32_t mMediaHandlerType; + Vector mSampleDescs; + + // from track extends: + uint32_t mDefaultSampleDescriptionIndex; + uint32_t mDefaultSampleDuration; + uint32_t mDefaultSampleSize; + uint32_t mDefaultSampleFlags; + + uint32_t mDecodingTime; + + sp mStaticFragment; + List > mFragments; + }; + + struct TrackFragmentHeaderInfo { + enum Flags { + kBaseDataOffsetPresent = 0x01, + kSampleDescriptionIndexPresent = 0x02, + kDefaultSampleDurationPresent = 0x08, + kDefaultSampleSizePresent = 0x10, + kDefaultSampleFlagsPresent = 0x20, + kDurationIsEmpty = 0x10000, + }; + + uint32_t mTrackID; + uint32_t mFlags; + uint64_t mBaseDataOffset; + uint32_t mSampleDescriptionIndex; + uint32_t mDefaultSampleDuration; + uint32_t mDefaultSampleSize; + uint32_t mDefaultSampleFlags; + + uint64_t mDataOffset; + }; + + static const DispatchEntry kDispatchTable[]; + + sp mSource; + off_t mBufferPos; + bool mSuspended; + sp mBuffer; + Vector mStack; + KeyedVector mTracks; // TrackInfo by trackID + Vector mMediaData; + + uint32_t mCurrentTrackID; + + TrackFragmentHeaderInfo mTrackFragmentHeaderInfo; + + status_t onProceed(); + status_t onDequeueAccessUnit(size_t trackIndex, sp *accessUnit); + + void enter(uint32_t type, uint64_t size); + + uint16_t readU16(size_t offset); + uint32_t readU32(size_t offset); + uint64_t readU64(size_t offset); + void skip(off_t distance); + status_t need(size_t size); + bool fitsContainer(uint64_t size) const; + + status_t parseTrackHeader( + uint32_t type, size_t offset, uint64_t size); + + status_t parseMediaHeader( + uint32_t type, size_t offset, uint64_t size); + + status_t parseMediaHandler( + uint32_t type, size_t offset, uint64_t size); + + status_t parseTrackExtends( + uint32_t type, size_t offset, uint64_t size); + + status_t parseTrackFragmentHeader( + uint32_t type, size_t offset, uint64_t size); + + status_t parseTrackFragmentRun( + uint32_t type, size_t offset, uint64_t size); + + status_t parseVisualSampleEntry( + uint32_t type, size_t offset, uint64_t size); + + status_t parseAudioSampleEntry( + uint32_t type, size_t offset, uint64_t size); + + status_t parseSampleSizes( + uint32_t type, size_t offset, uint64_t size); + + status_t parseCompactSampleSizes( + uint32_t type, size_t offset, uint64_t size); + + status_t parseSampleToChunk( + uint32_t type, size_t offset, uint64_t size); + + status_t parseChunkOffsets( + uint32_t type, size_t offset, uint64_t size); + + status_t parseChunkOffsets64( + uint32_t type, size_t offset, uint64_t size); + + status_t parseAVCCodecSpecificData( + uint32_t type, size_t offset, uint64_t size); + + status_t parseESDSCodecSpecificData( + uint32_t type, size_t offset, uint64_t size); + + status_t parseMediaData( + uint32_t type, size_t offset, uint64_t size); + + TrackInfo *editTrack(uint32_t trackID, bool createIfNecessary = false); + + ssize_t findTrack(bool wantAudio) const; + + status_t makeAccessUnit( + TrackInfo *info, + const SampleInfo &sample, + const MediaDataInfo &mdatInfo, + sp *accessUnit); + + status_t getSample( + TrackInfo *info, + sp *fragment, + SampleInfo *sampleInfo); + + static int CompareSampleLocation( + const SampleInfo &sample, const MediaDataInfo &mdatInfo); + + void resumeIfNecessary(); + + void copyBuffer( + sp *dst, + size_t offset, uint64_t size, size_t extra = 0) const; + + DISALLOW_EVIL_CONSTRUCTORS(Parser); +}; + +} // namespace android + +#endif // PARSER_H_ + diff --git a/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.cpp b/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.cpp new file mode 100644 index 0000000..e2df468 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2012 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 "TrackFragment" +#include + +#include "TrackFragment.h" + +#include +#include +#include +#include + +namespace android { + +Parser::DynamicTrackFragment::DynamicTrackFragment() + : mComplete(false), + mSampleIndex(0) { +} + +Parser::DynamicTrackFragment::~DynamicTrackFragment() { +} + +status_t Parser::DynamicTrackFragment::getSample(SampleInfo *info) { + if (mSampleIndex >= mSamples.size()) { + return mComplete ? ERROR_END_OF_STREAM : -EWOULDBLOCK; + } + + *info = mSamples.itemAt(mSampleIndex); + + return OK; +} + +void Parser::DynamicTrackFragment::advance() { + ++mSampleIndex; +} + +void Parser::DynamicTrackFragment::addSample( + off64_t dataOffset, size_t sampleSize, + uint32_t presentationTime, + size_t sampleDescIndex, + uint32_t flags) { + mSamples.push(); + SampleInfo *sampleInfo = &mSamples.editItemAt(mSamples.size() - 1); + + sampleInfo->mOffset = dataOffset; + sampleInfo->mSize = sampleSize; + sampleInfo->mPresentationTime = presentationTime; + sampleInfo->mSampleDescIndex = sampleDescIndex; + sampleInfo->mFlags = flags; +} + +status_t Parser::DynamicTrackFragment::signalCompletion() { + mComplete = true; + + return OK; +} + +bool Parser::DynamicTrackFragment::complete() const { + return mComplete; +} + +//////////////////////////////////////////////////////////////////////////////// + +Parser::StaticTrackFragment::StaticTrackFragment() + : mSampleIndex(0), + mSampleCount(0), + mSampleToChunkIndex(-1), + mSampleToChunkRemaining(0), + mPrevChunkIndex(0xffffffff), + mNextSampleOffset(0) { +} + +Parser::StaticTrackFragment::~StaticTrackFragment() { +} + +status_t Parser::StaticTrackFragment::getSample(SampleInfo *info) { + if (mSampleIndex >= mSampleCount) { + return ERROR_END_OF_STREAM; + } + + *info = mSampleInfo; + + ALOGV("returning sample %d at [0x%08llx, 0x%08llx)", + mSampleIndex, + info->mOffset, info->mOffset + info->mSize); + + return OK; +} + +void Parser::StaticTrackFragment::updateSampleInfo() { + if (mSampleIndex >= mSampleCount) { + return; + } + + if (mSampleSizes != NULL) { + uint32_t defaultSampleSize = U32_AT(mSampleSizes->data() + 4); + if (defaultSampleSize > 0) { + mSampleInfo.mSize = defaultSampleSize; + } else { + mSampleInfo.mSize= U32_AT(mSampleSizes->data() + 12 + 4 * mSampleIndex); + } + } else { + CHECK(mCompactSampleSizes != NULL); + + uint32_t fieldSize = U32_AT(mCompactSampleSizes->data() + 4); + + switch (fieldSize) { + case 4: + { + unsigned byte = mCompactSampleSizes->data()[12 + mSampleIndex / 2]; + mSampleInfo.mSize = (mSampleIndex & 1) ? byte & 0x0f : byte >> 4; + break; + } + + case 8: + { + mSampleInfo.mSize = mCompactSampleSizes->data()[12 + mSampleIndex]; + break; + } + + default: + { + CHECK_EQ(fieldSize, 16); + mSampleInfo.mSize = + U16_AT(mCompactSampleSizes->data() + 12 + mSampleIndex * 2); + break; + } + } + } + + CHECK_GT(mSampleToChunkRemaining, 0); + + // The sample desc index is 1-based... XXX + mSampleInfo.mSampleDescIndex = + U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 8); + + uint32_t chunkIndex = + U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex); + + CHECK_GE(chunkIndex, 1); + --chunkIndex; + + if (chunkIndex != mPrevChunkIndex) { + mPrevChunkIndex = chunkIndex; + + if (mChunkOffsets != NULL) { + uint32_t entryCount = U32_AT(mChunkOffsets->data() + 4); + + if (chunkIndex >= entryCount) { + mSampleIndex = mSampleCount; + return; + } + + mNextSampleOffset = + U32_AT(mChunkOffsets->data() + 8 + 4 * chunkIndex); + } else { + CHECK(mChunkOffsets64 != NULL); + + uint32_t entryCount = U32_AT(mChunkOffsets64->data() + 4); + + if (chunkIndex >= entryCount) { + mSampleIndex = mSampleCount; + return; + } + + mNextSampleOffset = + U64_AT(mChunkOffsets64->data() + 8 + 8 * chunkIndex); + } + } + + mSampleInfo.mOffset = mNextSampleOffset; + + mSampleInfo.mPresentationTime = 0; + mSampleInfo.mFlags = 0; +} + +void Parser::StaticTrackFragment::advance() { + mNextSampleOffset += mSampleInfo.mSize; + + ++mSampleIndex; + if (--mSampleToChunkRemaining == 0) { + uint32_t entryCount = U32_AT(mSampleToChunk->data() + 4); + + if ((uint32_t)(mSampleToChunkIndex + 1) == entryCount) { + mSampleIndex = mSampleCount; // EOS. + return; + } + + ++mSampleToChunkIndex; + mSampleToChunkRemaining = + U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 4); + } + + updateSampleInfo(); +} + +static void setU32At(uint8_t *ptr, uint32_t x) { + ptr[0] = x >> 24; + ptr[1] = (x >> 16) & 0xff; + ptr[2] = (x >> 8) & 0xff; + ptr[3] = x & 0xff; +} + +void Parser::StaticTrackFragment::fixSampleToChunkTableIfNecessary() { + uint32_t entryCount = U32_AT(mSampleToChunk->data() + 4); + uint32_t totalSamples = 0; + for (uint32_t i = 0; i < entryCount; ++i) { + totalSamples += U32_AT(mSampleToChunk->data() + 8 + 12 * i + 4); + } + + if (totalSamples < mSampleCount) { + // Some samples are not accounted for in the sample-to-chunk + // data. Fabricate an extra chunk adjacent to the last one + // in the table with the same sample desription index. + + ALOGW("Faking an extra sample-to-chunk entry for %d samples.", + mSampleCount - totalSamples); + + uint32_t lastChunkIndex = + U32_AT(mSampleToChunk->data() + 8 + 12 * (entryCount - 1)); + + uint32_t lastSampleDescriptionIndex = + U32_AT(mSampleToChunk->data() + 8 + 12 * (entryCount - 1) + 8); + + uint8_t *ptr = mSampleToChunk->data() + 8 + 12 * entryCount; + + setU32At(ptr, lastChunkIndex + 1); + setU32At(ptr + 4, mSampleCount - totalSamples); + setU32At(ptr + 8, lastSampleDescriptionIndex); + setU32At(mSampleToChunk->data() + 4, entryCount + 1); + } +} + +status_t Parser::StaticTrackFragment::signalCompletion() { + fixSampleToChunkTableIfNecessary(); + + mSampleToChunkIndex = 0; + + mSampleToChunkRemaining = + U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 4); + + updateSampleInfo(); + + return OK; +} + +bool Parser::StaticTrackFragment::complete() const { + return true; +} + +status_t Parser::StaticTrackFragment::parseSampleSizes( + Parser *parser, uint32_t type, size_t offset, uint64_t size) { + if (offset + 12 > size) { + return ERROR_MALFORMED; + } + + if (parser->readU32(offset) != 0) { + return ERROR_MALFORMED; + } + + uint32_t sampleSize = parser->readU32(offset + 4); + uint32_t sampleCount = parser->readU32(offset + 8); + + if (sampleSize == 0 && offset + 12 + sampleCount * 4 != size) { + return ERROR_MALFORMED; + } + + parser->copyBuffer(&mSampleSizes, offset, size); + + mSampleCount = sampleCount; + + return OK; +} + +status_t Parser::StaticTrackFragment::parseCompactSampleSizes( + Parser *parser, uint32_t type, size_t offset, uint64_t size) { + if (offset + 12 > size) { + return ERROR_MALFORMED; + } + + if (parser->readU32(offset) != 0) { + return ERROR_MALFORMED; + } + + uint32_t fieldSize = parser->readU32(offset + 4); + + if (fieldSize != 4 && fieldSize != 8 && fieldSize != 16) { + return ERROR_MALFORMED; + } + + uint32_t sampleCount = parser->readU32(offset + 8); + + if (offset + 12 + (sampleCount * fieldSize + 4) / 8 != size) { + return ERROR_MALFORMED; + } + + parser->copyBuffer(&mCompactSampleSizes, offset, size); + + mSampleCount = sampleCount; + + return OK; +} + +status_t Parser::StaticTrackFragment::parseSampleToChunk( + Parser *parser, uint32_t type, size_t offset, uint64_t size) { + if (offset + 8 > size) { + return ERROR_MALFORMED; + } + + if (parser->readU32(offset) != 0) { + return ERROR_MALFORMED; + } + + uint32_t entryCount = parser->readU32(offset + 4); + + if (entryCount == 0 || offset + 8 + entryCount * 12 != size) { + return ERROR_MALFORMED; + } + + parser->copyBuffer(&mSampleToChunk, offset, size, 12 /* extra */); + + return OK; +} + +status_t Parser::StaticTrackFragment::parseChunkOffsets( + Parser *parser, uint32_t type, size_t offset, uint64_t size) { + if (offset + 8 > size) { + return ERROR_MALFORMED; + } + + if (parser->readU32(offset) != 0) { + return ERROR_MALFORMED; + } + + uint32_t entryCount = parser->readU32(offset + 4); + + if (offset + 8 + entryCount * 4 != size) { + return ERROR_MALFORMED; + } + + parser->copyBuffer(&mChunkOffsets, offset, size); + + return OK; +} + +status_t Parser::StaticTrackFragment::parseChunkOffsets64( + Parser *parser, uint32_t type, size_t offset, uint64_t size) { + if (offset + 8 > size) { + return ERROR_MALFORMED; + } + + if (parser->readU32(offset) != 0) { + return ERROR_MALFORMED; + } + + uint32_t entryCount = parser->readU32(offset + 4); + + if (offset + 8 + entryCount * 8 != size) { + return ERROR_MALFORMED; + } + + parser->copyBuffer(&mChunkOffsets64, offset, size); + + return OK; +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.h b/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.h new file mode 100644 index 0000000..e5945ac --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/mp4/TrackFragment.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 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 TRACK_FRAGMENT_H_ + +#define TRACK_FRAGMENT_H_ + +#include "Parser.h" + +namespace android { + +struct Parser::TrackFragment : public RefBase { + TrackFragment() {} + + virtual status_t getSample(SampleInfo *info) = 0; + virtual void advance() = 0; + + virtual status_t signalCompletion() = 0; + virtual bool complete() const = 0; + +protected: + virtual ~TrackFragment() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(TrackFragment); +}; + +struct Parser::DynamicTrackFragment : public Parser::TrackFragment { + DynamicTrackFragment(); + + virtual status_t getSample(SampleInfo *info); + virtual void advance(); + + void addSample( + off64_t dataOffset, size_t sampleSize, + uint32_t presentationTime, + size_t sampleDescIndex, + uint32_t flags); + + // No more samples will be added to this fragment. + virtual status_t signalCompletion(); + + virtual bool complete() const; + +protected: + virtual ~DynamicTrackFragment(); + +private: + bool mComplete; + size_t mSampleIndex; + Vector mSamples; + + DISALLOW_EVIL_CONSTRUCTORS(DynamicTrackFragment); +}; + +struct Parser::StaticTrackFragment : public Parser::TrackFragment { + StaticTrackFragment(); + + virtual status_t getSample(SampleInfo *info); + virtual void advance(); + + virtual status_t signalCompletion(); + virtual bool complete() const; + + status_t parseSampleSizes( + Parser *parser, uint32_t type, size_t offset, uint64_t size); + + status_t parseCompactSampleSizes( + Parser *parser, uint32_t type, size_t offset, uint64_t size); + + status_t parseSampleToChunk( + Parser *parser, uint32_t type, size_t offset, uint64_t size); + + status_t parseChunkOffsets( + Parser *parser, uint32_t type, size_t offset, uint64_t size); + + status_t parseChunkOffsets64( + Parser *parser, uint32_t type, size_t offset, uint64_t size); + +protected: + virtual ~StaticTrackFragment(); + +private: + size_t mSampleIndex; + size_t mSampleCount; + + SampleInfo mSampleInfo; + + sp mSampleSizes; + sp mCompactSampleSizes; + + sp mSampleToChunk; + ssize_t mSampleToChunkIndex; + size_t mSampleToChunkRemaining; + + sp mChunkOffsets; + sp mChunkOffsets64; + uint32_t mPrevChunkIndex; + uint64_t mNextSampleOffset; + + void updateSampleInfo(); + void fixSampleToChunkTableIfNecessary(); + + DISALLOW_EVIL_CONSTRUCTORS(StaticTrackFragment); +}; + +} // namespace android + +#endif // TRACK_FRAGMENT_H_ -- cgit v1.1