From f933441648ef6a71dee783d733aac17b9508b452 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 15 Dec 2010 15:17:42 -0800 Subject: Initial support for a true streaming player for mpeg2 transport streams. Change-Id: I153eec439d260a5524b21270e16d36940ec3161a --- cmds/stagefright/Android.mk | 26 ++ cmds/stagefright/sf2.cpp | 564 ++++++++++++++++++++++++++++++++++++++++++++ cmds/stagefright/stream.cpp | 23 +- 3 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 cmds/stagefright/sf2.cpp (limited to 'cmds') 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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "include/ESDS.h" + +using namespace android; + +struct Controller : public AHandler { + Controller(const char *uri, bool decodeAudio, const sp &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 &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::CreateFromURI(mURI.c_str()); + + sp extractor = + MediaExtractor::Create(dataSource); + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp 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 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 mDecodeLooper; + + AString mURI; + bool mDecodeAudio; + sp mSurface; + sp mCodec; + sp mSource; + + Vector > 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 makeFormat(const sp &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp msg = new AMessage; + msg->setString("mime", mime); + + if (!strncasecmp("video/", mime, 6)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + msg->setInt32("width", width); + msg->setInt32("height", height); + } else { + CHECK(!strncasecmp("audio/", mime, 6)); + + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + msg->setInt32("channel-count", numChannels); + msg->setInt32("sample-rate", sampleRate); + } + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + // Parse the AVCDecoderConfigurationRecord + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + + // There is decodable content out there that fails the following + // assertion, let's be lenient for now... + // CHECK((ptr[4] >> 2) == 0x3f); // reserved + + size_t lengthSize = 1 + (ptr[4] & 3); + + // commented out check below as H264_QVGA_500_NO_AUDIO.3gp + // violates it... + // CHECK((ptr[5] >> 5) == 7); // reserved + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + sp buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + CHECK(size >= 1); + size_t numPictureParameterSets = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPictureParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + msg->setObject("csd", buffer); + } else if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + sp buffer = new ABuffer(codec_specific_data_size); + + memcpy(buffer->data(), codec_specific_data, + codec_specific_data_size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + return msg; + } + + void onFillThisBuffer(const sp &msg) { + sp reply; + CHECK(msg->findMessage("reply", &reply)); + + if (mSeekState == SEEK_FLUSHING) { + reply->post(); + return; + } + + sp obj; + CHECK(msg->findObject("buffer", &obj)); + sp outBuffer = static_cast(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 &msg) { + sp obj; + CHECK(msg->findObject("buffer", &obj)); + + sp buffer = static_cast(obj.get()); + mTotalBytesReceived += buffer->size(); + + sp 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 looper = new ALooper; + looper->setName("sf2"); + + sp composerClient; + sp control; + sp 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 = 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 mListener; Vector > 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 > &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 mem = mBuffers.itemAt(index); ssize_t n = read(mFd, mem->pointer(), mem->size()); -- cgit v1.1