diff options
author | Andreas Huber <andih@google.com> | 2012-02-21 11:47:18 -0800 |
---|---|---|
committer | Andreas Huber <andih@google.com> | 2012-02-22 15:06:06 -0800 |
commit | 5778822d86b0337407514b9372562b86edfa91cd (patch) | |
tree | b8faf8188dfb8865bd88b4dfc778a7d9ab89bb73 /cmds | |
parent | c33305c5dd4cc06e71eb0c66a7150aa6ab647c99 (diff) | |
download | frameworks_av-5778822d86b0337407514b9372562b86edfa91cd.zip frameworks_av-5778822d86b0337407514b9372562b86edfa91cd.tar.gz frameworks_av-5778822d86b0337407514b9372562b86edfa91cd.tar.bz2 |
Implementation of a java media codec interface and associated tools.
Change-Id: I13e54062d4de584355c5d82bb027a68aeaf2923b
Diffstat (limited to 'cmds')
-rw-r--r-- | cmds/stagefright/Android.mk | 24 | ||||
-rw-r--r-- | cmds/stagefright/SimplePlayer.cpp | 624 | ||||
-rw-r--r-- | cmds/stagefright/SimplePlayer.h | 109 | ||||
-rw-r--r-- | cmds/stagefright/codec.cpp | 409 | ||||
-rw-r--r-- | cmds/stagefright/sf2.cpp | 4 |
5 files changed, 1167 insertions, 3 deletions
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 11e94e8..f26747b 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -147,4 +147,28 @@ LOCAL_MODULE:= sf2 include $(BUILD_EXECUTABLE) +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + codec.cpp \ + SimplePlayer.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder libstagefright_foundation \ + libmedia libgui 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:= codec + +include $(BUILD_EXECUTABLE) diff --git a/cmds/stagefright/SimplePlayer.cpp b/cmds/stagefright/SimplePlayer.cpp new file mode 100644 index 0000000..d5b9223 --- /dev/null +++ b/cmds/stagefright/SimplePlayer.cpp @@ -0,0 +1,624 @@ +/* + * 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 "SimplePlayer" +#include <utils/Log.h> + +#include "SimplePlayer.h" + +#include <gui/SurfaceTextureClient.h> +#include <media/AudioTrack.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaCodec.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/NativeWindowWrapper.h> +#include <media/stagefright/NuMediaExtractor.h> + +namespace android { + +SimplePlayer::SimplePlayer() + : mState(UNINITIALIZED), + mDoMoreStuffGeneration(0), + mStartTimeRealUs(-1ll) { +} + +SimplePlayer::~SimplePlayer() { +} + +// static +status_t PostAndAwaitResponse( + const sp<AMessage> &msg, sp<AMessage> *response) { + status_t err = msg->postAndAwaitResponse(response); + + if (err != OK) { + return err; + } + + if (!(*response)->findInt32("err", &err)) { + err = OK; + } + + return err; +} +status_t SimplePlayer::setDataSource(const char *path) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); + msg->setString("path", path); + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t SimplePlayer::setSurface(const sp<ISurfaceTexture> &surfaceTexture) { + sp<AMessage> msg = new AMessage(kWhatSetSurface, id()); + + sp<SurfaceTextureClient> surfaceTextureClient; + if (surfaceTexture != NULL) { + surfaceTextureClient = new SurfaceTextureClient(surfaceTexture); + } + + msg->setObject( + "native-window", new NativeWindowWrapper(surfaceTextureClient)); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t SimplePlayer::prepare() { + sp<AMessage> msg = new AMessage(kWhatPrepare, id()); + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t SimplePlayer::start() { + sp<AMessage> msg = new AMessage(kWhatStart, id()); + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t SimplePlayer::stop() { + sp<AMessage> msg = new AMessage(kWhatStop, id()); + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +status_t SimplePlayer::reset() { + sp<AMessage> msg = new AMessage(kWhatReset, id()); + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +void SimplePlayer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatSetDataSource: + { + status_t err; + if (mState != UNINITIALIZED) { + err = INVALID_OPERATION; + } else { + CHECK(msg->findString("path", &mPath)); + mState = UNPREPARED; + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatSetSurface: + { + status_t err; + if (mState != UNPREPARED) { + err = INVALID_OPERATION; + } else { + sp<RefBase> obj; + CHECK(msg->findObject("native-window", &obj)); + + mNativeWindow = static_cast<NativeWindowWrapper *>(obj.get()); + + err = OK; + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatPrepare: + { + status_t err; + if (mState != UNPREPARED) { + err = INVALID_OPERATION; + } else { + err = onPrepare(); + + if (err == OK) { + mState = STOPPED; + } + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatStart: + { + status_t err = OK; + + if (mState == UNPREPARED) { + err = onPrepare(); + + if (err == OK) { + mState = STOPPED; + } + } + + if (err == OK) { + if (mState != STOPPED) { + err = INVALID_OPERATION; + } else { + err = onStart(); + + if (err == OK) { + mState = STARTED; + } + } + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatStop: + { + status_t err; + + if (mState != STARTED) { + err = INVALID_OPERATION; + } else { + err = onStop(); + + if (err == OK) { + mState = STOPPED; + } + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatReset: + { + status_t err = OK; + + if (mState == STARTED) { + CHECK_EQ(onStop(), (status_t)OK); + mState = STOPPED; + } + + if (mState == STOPPED) { + err = onReset(); + mState = UNINITIALIZED; + } + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + sp<AMessage> response = new AMessage; + response->setInt32("err", err); + response->postReply(replyID); + break; + } + + case kWhatDoMoreStuff: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mDoMoreStuffGeneration) { + break; + } + + status_t err = onDoMoreStuff(); + + if (err == OK) { + msg->post(5000ll); + } + break; + } + + default: + TRESPASS(); + } +} + +status_t SimplePlayer::onPrepare() { + CHECK_EQ(mState, UNPREPARED); + + mExtractor = new NuMediaExtractor; + + status_t err = mExtractor->setDataSource(mPath.c_str()); + + if (err != OK) { + mExtractor.clear(); + return err; + } + + if (mCodecLooper == NULL) { + mCodecLooper = new ALooper; + mCodecLooper->start(); + } + + bool haveAudio = false; + bool haveVideo = false; + for (size_t i = 0; i < mExtractor->countTracks(); ++i) { + sp<AMessage> format; + status_t err = mExtractor->getTrackFormat(i, &format); + CHECK_EQ(err, (status_t)OK); + + AString mime; + CHECK(format->findString("mime", &mime)); + + if (!haveAudio && !strncasecmp(mime.c_str(), "audio/", 6)) { + haveAudio = true; + } else if (!haveVideo && !strncasecmp(mime.c_str(), "video/", 6)) { + haveVideo = true; + } else { + continue; + } + + err = mExtractor->selectTrack(i); + CHECK_EQ(err, (status_t)OK); + + CodecState *state = + &mStateByTrackIndex.editValueAt( + mStateByTrackIndex.add(i, CodecState())); + + state->mNumFramesWritten = 0; + state->mCodec = MediaCodec::CreateByType( + mCodecLooper, mime.c_str(), false /* encoder */); + + CHECK(state->mCodec != NULL); + + err = state->mCodec->configure( + format, mNativeWindow->getSurfaceTextureClient(), + 0 /* flags */); + + CHECK_EQ(err, (status_t)OK); + + size_t j = 0; + sp<RefBase> obj; + while (format->findObject(StringPrintf("csd-%d", j).c_str(), &obj)) { + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + state->mCSD.push_back(buffer); + + ++j; + } + } + + for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { + CodecState *state = &mStateByTrackIndex.editValueAt(i); + + status_t err = state->mCodec->start(); + CHECK_EQ(err, (status_t)OK); + + err = state->mCodec->getInputBuffers(&state->mBuffers[0]); + CHECK_EQ(err, (status_t)OK); + + err = state->mCodec->getOutputBuffers(&state->mBuffers[1]); + CHECK_EQ(err, (status_t)OK); + + for (size_t j = 0; j < state->mCSD.size(); ++j) { + const sp<ABuffer> &srcBuffer = state->mCSD.itemAt(j); + + size_t index; + err = state->mCodec->dequeueInputBuffer(&index, -1ll); + CHECK_EQ(err, (status_t)OK); + + const sp<ABuffer> &dstBuffer = state->mBuffers[0].itemAt(index); + + CHECK_LE(srcBuffer->size(), dstBuffer->capacity()); + dstBuffer->setRange(0, srcBuffer->size()); + memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size()); + + err = state->mCodec->queueInputBuffer( + index, + 0, + dstBuffer->size(), + 0ll, + MediaCodec::BUFFER_FLAG_CODECCONFIG); + CHECK_EQ(err, (status_t)OK); + } + } + + return OK; +} + +status_t SimplePlayer::onStart() { + CHECK_EQ(mState, STOPPED); + + mStartTimeRealUs = -1ll; + + sp<AMessage> msg = new AMessage(kWhatDoMoreStuff, id()); + msg->setInt32("generation", ++mDoMoreStuffGeneration); + msg->post(); + + return OK; +} + +status_t SimplePlayer::onStop() { + CHECK_EQ(mState, STARTED); + + ++mDoMoreStuffGeneration; + + return OK; +} + +status_t SimplePlayer::onReset() { + CHECK_EQ(mState, STOPPED); + + for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { + CodecState *state = &mStateByTrackIndex.editValueAt(i); + + CHECK_EQ(state->mCodec->stop(), (status_t)OK); + } + + mStartTimeRealUs = -1ll; + + mStateByTrackIndex.clear(); + mCodecLooper.clear(); + mExtractor.clear(); + mNativeWindow.clear(); + mPath.clear(); + + return OK; +} + +status_t SimplePlayer::onDoMoreStuff() { + for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { + CodecState *state = &mStateByTrackIndex.editValueAt(i); + + size_t index; + status_t err = state->mCodec->dequeueInputBuffer(&index); + + if (err == OK) { + state->mAvailInputBufferIndices.push_back(index); + } + + BufferInfo info; + err = state->mCodec->dequeueOutputBuffer( + &info.mIndex, + &info.mOffset, + &info.mSize, + &info.mPresentationTimeUs, + &info.mFlags); + + if (err == OK) { + state->mAvailOutputBufferInfos.push_back(info); + } else if (err == INFO_FORMAT_CHANGED) { + err = onOutputFormatChanged(mStateByTrackIndex.keyAt(i), state); + CHECK_EQ(err, (status_t)OK); + } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) { + err = state->mCodec->getOutputBuffers(&state->mBuffers[1]); + CHECK_EQ(err, (status_t)OK); + } + } + + for (;;) { + size_t trackIndex; + status_t err = mExtractor->getSampleTrackIndex(&trackIndex); + + if (err != OK) { + ALOGI("encountered input EOS."); + break; + } else { + CodecState *state = &mStateByTrackIndex.editValueFor(trackIndex); + + if (state->mAvailInputBufferIndices.empty()) { + break; + } + + size_t index = *state->mAvailInputBufferIndices.begin(); + state->mAvailInputBufferIndices.erase( + state->mAvailInputBufferIndices.begin()); + + const sp<ABuffer> &dstBuffer = + state->mBuffers[0].itemAt(index); + + err = mExtractor->readSampleData(dstBuffer); + CHECK_EQ(err, (status_t)OK); + + int64_t timeUs; + CHECK_EQ(mExtractor->getSampleTime(&timeUs), (status_t)OK); + + err = state->mCodec->queueInputBuffer( + index, + dstBuffer->offset(), + dstBuffer->size(), + timeUs, + 0); + CHECK_EQ(err, (status_t)OK); + + err = mExtractor->advance(); + CHECK_EQ(err, (status_t)OK); + } + } + + int64_t nowUs = ALooper::GetNowUs(); + + if (mStartTimeRealUs < 0ll) { + mStartTimeRealUs = nowUs + 1000000ll; + } + + for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { + CodecState *state = &mStateByTrackIndex.editValueAt(i); + + while (!state->mAvailOutputBufferInfos.empty()) { + BufferInfo *info = &*state->mAvailOutputBufferInfos.begin(); + + int64_t whenRealUs = info->mPresentationTimeUs + mStartTimeRealUs; + int64_t lateByUs = nowUs - whenRealUs; + + if (lateByUs > -10000ll) { + bool release = true; + + if (lateByUs > 30000ll) { + ALOGI("track %d buffer late by %lld us, dropping.", + i, lateByUs); + state->mCodec->releaseOutputBuffer(info->mIndex); + } else { + if (state->mAudioTrack != NULL) { + const sp<ABuffer> &srcBuffer = + state->mBuffers[1].itemAt(info->mIndex); + + renderAudio(state, info, srcBuffer); + + if (info->mSize > 0) { + release = false; + } + } + + if (release) { + state->mCodec->renderOutputBufferAndRelease( + info->mIndex); + } + } + + if (release) { + state->mAvailOutputBufferInfos.erase( + state->mAvailOutputBufferInfos.begin()); + + info = NULL; + } else { + break; + } + } else { + ALOGV("track %d buffer early by %lld us.", i, -lateByUs); + break; + } + } + } + + return OK; +} + +status_t SimplePlayer::onOutputFormatChanged( + size_t trackIndex, CodecState *state) { + sp<AMessage> format; + status_t err = state->mCodec->getOutputFormat(&format); + + if (err != OK) { + return err; + } + + AString mime; + CHECK(format->findString("mime", &mime)); + + if (!strncasecmp(mime.c_str(), "audio/", 6)) { + int32_t channelCount; + int32_t sampleRate; + CHECK(format->findInt32("channel-count", &channelCount)); + CHECK(format->findInt32("sample-rate", &sampleRate)); + + state->mAudioTrack = new AudioTrack( + AUDIO_STREAM_MUSIC, + sampleRate, + AUDIO_FORMAT_PCM_16_BIT, + (channelCount == 1) + ? AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO, + 0); + + state->mNumFramesWritten = 0; + } + + return OK; +} + +void SimplePlayer::renderAudio( + CodecState *state, BufferInfo *info, const sp<ABuffer> &buffer) { + CHECK(state->mAudioTrack != NULL); + + if (state->mAudioTrack->stopped()) { + state->mAudioTrack->start(); + } + + uint32_t numFramesPlayed; + CHECK_EQ(state->mAudioTrack->getPosition(&numFramesPlayed), (status_t)OK); + + uint32_t numFramesAvailableToWrite = + state->mAudioTrack->frameCount() + - (state->mNumFramesWritten - numFramesPlayed); + + size_t numBytesAvailableToWrite = + numFramesAvailableToWrite * state->mAudioTrack->frameSize(); + + size_t copy = info->mSize; + if (copy > numBytesAvailableToWrite) { + copy = numBytesAvailableToWrite; + } + + if (copy == 0) { + return; + } + + int64_t startTimeUs = ALooper::GetNowUs(); + + ssize_t nbytes = state->mAudioTrack->write( + buffer->base() + info->mOffset, copy); + + CHECK_EQ(nbytes, (ssize_t)copy); + + int64_t delayUs = ALooper::GetNowUs() - startTimeUs; + + uint32_t numFramesWritten = nbytes / state->mAudioTrack->frameSize(); + + if (delayUs > 2000ll) { + ALOGW("AudioTrack::write took %lld us, numFramesAvailableToWrite=%u, " + "numFramesWritten=%u", + delayUs, numFramesAvailableToWrite, numFramesWritten); + } + + info->mOffset += nbytes; + info->mSize -= nbytes; + + state->mNumFramesWritten += numFramesWritten; +} + +} // namespace android diff --git a/cmds/stagefright/SimplePlayer.h b/cmds/stagefright/SimplePlayer.h new file mode 100644 index 0000000..2548252 --- /dev/null +++ b/cmds/stagefright/SimplePlayer.h @@ -0,0 +1,109 @@ +/* + * 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 <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/AString.h> +#include <utils/KeyedVector.h> + +namespace android { + +struct ABuffer; +struct ALooper; +struct AudioTrack; +struct ISurfaceTexture; +struct MediaCodec; +struct NativeWindowWrapper; +struct NuMediaExtractor; + +struct SimplePlayer : public AHandler { + SimplePlayer(); + + status_t setDataSource(const char *path); + status_t setSurface(const sp<ISurfaceTexture> &surfaceTexture); + status_t prepare(); + status_t start(); + status_t stop(); + status_t reset(); + +protected: + virtual ~SimplePlayer(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum State { + UNINITIALIZED, + UNPREPARED, + STOPPED, + STARTED + }; + + enum { + kWhatSetDataSource, + kWhatSetSurface, + kWhatPrepare, + kWhatStart, + kWhatStop, + kWhatReset, + kWhatDoMoreStuff, + }; + + struct BufferInfo { + size_t mIndex; + size_t mOffset; + size_t mSize; + int64_t mPresentationTimeUs; + uint32_t mFlags; + }; + + struct CodecState + { + sp<MediaCodec> mCodec; + Vector<sp<ABuffer> > mCSD; + Vector<sp<ABuffer> > mBuffers[2]; + + List<size_t> mAvailInputBufferIndices; + List<BufferInfo> mAvailOutputBufferInfos; + + sp<AudioTrack> mAudioTrack; + uint32_t mNumFramesWritten; + }; + + State mState; + AString mPath; + sp<NativeWindowWrapper> mNativeWindow; + + sp<NuMediaExtractor> mExtractor; + sp<ALooper> mCodecLooper; + KeyedVector<size_t, CodecState> mStateByTrackIndex; + int32_t mDoMoreStuffGeneration; + + int64_t mStartTimeRealUs; + + status_t onPrepare(); + status_t onStart(); + status_t onStop(); + status_t onReset(); + status_t onDoMoreStuff(); + status_t onOutputFormatChanged(size_t trackIndex, CodecState *state); + + void renderAudio( + CodecState *state, BufferInfo *info, const sp<ABuffer> &buffer); + + DISALLOW_EVIL_CONSTRUCTORS(SimplePlayer); +}; + +} // namespace android diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp new file mode 100644 index 0000000..02dc8dd --- /dev/null +++ b/cmds/stagefright/codec.cpp @@ -0,0 +1,409 @@ +/* + * 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 "codec" +#include <utils/Log.h> + +#include "SimplePlayer.h" + +#include <binder/ProcessState.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/DataSource.h> +#include <media/stagefright/MediaCodec.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/NuMediaExtractor.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +static void usage(const char *me) { + fprintf(stderr, "usage: %s [-a] use audio\n" + "\t\t[-v] use video\n" + "\t\t[-p] playback\n", me); + + exit(1); +} + +namespace android { + +struct CodecState { + sp<MediaCodec> mCodec; + Vector<sp<ABuffer> > mCSD; + size_t mCSDIndex; + Vector<sp<ABuffer> > mInBuffers; + Vector<sp<ABuffer> > mOutBuffers; + bool mSawOutputEOS; +}; + +} // namespace android + +static int decode( + const android::sp<android::ALooper> &looper, + const char *path, + bool useAudio, + bool useVideo) { + using namespace android; + + sp<NuMediaExtractor> extractor = new NuMediaExtractor; + if (extractor->setDataSource(path) != OK) { + fprintf(stderr, "unable to instantiate extractor.\n"); + return 1; + } + + KeyedVector<size_t, CodecState> stateByTrack; + + bool haveAudio = false; + bool haveVideo = false; + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<AMessage> format; + status_t err = extractor->getTrackFormat(i, &format); + CHECK_EQ(err, (status_t)OK); + + AString mime; + CHECK(format->findString("mime", &mime)); + + if (useAudio && !haveAudio + && !strncasecmp(mime.c_str(), "audio/", 6)) { + haveAudio = true; + } else if (useVideo && !haveVideo + && !strncasecmp(mime.c_str(), "video/", 6)) { + haveVideo = true; + } else { + continue; + } + + ALOGV("selecting track %d", i); + + err = extractor->selectTrack(i); + CHECK_EQ(err, (status_t)OK); + + CodecState *state = + &stateByTrack.editValueAt(stateByTrack.add(i, CodecState())); + + state->mCodec = MediaCodec::CreateByType( + looper, mime.c_str(), false /* encoder */); + + CHECK(state->mCodec != NULL); + + err = state->mCodec->configure( + format, NULL /* surfaceTexture */, 0 /* flags */); + + CHECK_EQ(err, (status_t)OK); + + size_t j = 0; + sp<RefBase> obj; + while (format->findObject(StringPrintf("csd-%d", j).c_str(), &obj)) { + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + state->mCSD.push_back(buffer); + + ++j; + } + + state->mCSDIndex = 0; + state->mSawOutputEOS = false; + + ALOGV("got %d pieces of codec specific data.", state->mCSD.size()); + } + + CHECK(!stateByTrack.isEmpty()); + + for (size_t i = 0; i < stateByTrack.size(); ++i) { + CodecState *state = &stateByTrack.editValueAt(i); + + sp<MediaCodec> codec = state->mCodec; + + CHECK_EQ((status_t)OK, codec->start()); + + CHECK_EQ((status_t)OK, codec->getInputBuffers(&state->mInBuffers)); + CHECK_EQ((status_t)OK, codec->getOutputBuffers(&state->mOutBuffers)); + + ALOGV("got %d input and %d output buffers", + state->mInBuffers.size(), state->mOutBuffers.size()); + + while (state->mCSDIndex < state->mCSD.size()) { + size_t index; + status_t err = codec->dequeueInputBuffer(&index); + + if (err == -EAGAIN) { + usleep(10000); + continue; + } + + CHECK_EQ(err, (status_t)OK); + + const sp<ABuffer> &srcBuffer = + state->mCSD.itemAt(state->mCSDIndex++); + + const sp<ABuffer> &buffer = state->mInBuffers.itemAt(index); + + memcpy(buffer->data(), srcBuffer->data(), srcBuffer->size()); + + err = codec->queueInputBuffer( + index, + 0 /* offset */, + srcBuffer->size(), + 0ll /* timeUs */, + MediaCodec::BUFFER_FLAG_CODECCONFIG); + + CHECK_EQ(err, (status_t)OK); + } + } + + bool sawInputEOS = false; + + for (;;) { + if (!sawInputEOS) { + size_t trackIndex; + status_t err = extractor->getSampleTrackIndex(&trackIndex); + + if (err != OK) { + ALOGV("signalling EOS."); + + for (size_t i = 0; i < stateByTrack.size(); ++i) { + CodecState *state = &stateByTrack.editValueAt(i); + + for (;;) { + size_t index; + err = state->mCodec->dequeueInputBuffer(&index); + + if (err == -EAGAIN) { + continue; + } + + CHECK_EQ(err, (status_t)OK); + + err = state->mCodec->queueInputBuffer( + index, + 0 /* offset */, + 0 /* size */, + 0ll /* timeUs */, + MediaCodec::BUFFER_FLAG_EOS); + + CHECK_EQ(err, (status_t)OK); + break; + } + } + + sawInputEOS = true; + } else { + CodecState *state = &stateByTrack.editValueFor(trackIndex); + + size_t index; + err = state->mCodec->dequeueInputBuffer(&index); + + if (err == OK) { + ALOGV("filling input buffer %d", index); + + const sp<ABuffer> &buffer = state->mInBuffers.itemAt(index); + + err = extractor->readSampleData(buffer); + CHECK_EQ(err, (status_t)OK); + + int64_t timeUs; + err = extractor->getSampleTime(&timeUs); + CHECK_EQ(err, (status_t)OK); + + err = state->mCodec->queueInputBuffer( + index, + 0 /* offset */, + buffer->size(), + timeUs, + 0 /* flags */); + + CHECK_EQ(err, (status_t)OK); + + extractor->advance(); + } else { + CHECK_EQ(err, -EAGAIN); + } + } + } + + bool sawOutputEOSOnAllTracks = true; + for (size_t i = 0; i < stateByTrack.size(); ++i) { + CodecState *state = &stateByTrack.editValueAt(i); + if (!state->mSawOutputEOS) { + sawOutputEOSOnAllTracks = false; + break; + } + } + + if (sawOutputEOSOnAllTracks) { + break; + } + + for (size_t i = 0; i < stateByTrack.size(); ++i) { + CodecState *state = &stateByTrack.editValueAt(i); + + if (state->mSawOutputEOS) { + continue; + } + + size_t index; + size_t offset; + size_t size; + int64_t presentationTimeUs; + uint32_t flags; + status_t err = state->mCodec->dequeueOutputBuffer( + &index, &offset, &size, &presentationTimeUs, &flags, + 10000ll); + + if (err == OK) { + ALOGV("draining output buffer %d, time = %lld us", + index, presentationTimeUs); + + err = state->mCodec->releaseOutputBuffer(index); + CHECK_EQ(err, (status_t)OK); + + if (flags & MediaCodec::BUFFER_FLAG_EOS) { + ALOGV("reached EOS on output."); + + state->mSawOutputEOS = true; + } + } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) { + ALOGV("INFO_OUTPUT_BUFFERS_CHANGED"); + CHECK_EQ((status_t)OK, + state->mCodec->getOutputBuffers(&state->mOutBuffers)); + + ALOGV("got %d output buffers", state->mOutBuffers.size()); + } else if (err == INFO_FORMAT_CHANGED) { + sp<AMessage> format; + CHECK_EQ((status_t)OK, state->mCodec->getOutputFormat(&format)); + + ALOGV("INFO_FORMAT_CHANGED: %s", format->debugString().c_str()); + } else { + CHECK_EQ(err, -EAGAIN); + } + } + } + + for (size_t i = 0; i < stateByTrack.size(); ++i) { + CodecState *state = &stateByTrack.editValueAt(i); + + CHECK_EQ((status_t)OK, state->mCodec->stop()); + } + + return 0; +} + +int main(int argc, char **argv) { + using namespace android; + + const char *me = argv[0]; + + bool useAudio = false; + bool useVideo = false; + bool playback = false; + + int res; + while ((res = getopt(argc, argv, "havp")) >= 0) { + switch (res) { + case 'a': + { + useAudio = true; + break; + } + + case 'v': + { + useVideo = true; + break; + } + + case 'p': + { + playback = true; + break; + } + + case '?': + case 'h': + default: + { + usage(me); + } + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(me); + } + + if (!useAudio && !useVideo) { + useAudio = useVideo = true; + } + + ProcessState::self()->startThreadPool(); + + DataSource::RegisterDefaultSniffers(); + + sp<ALooper> looper = new ALooper; + looper->start(); + + if (playback) { + sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient; + CHECK_EQ(composerClient->initCheck(), (status_t)OK); + + ssize_t displayWidth = composerClient->getDisplayWidth(0); + ssize_t displayHeight = composerClient->getDisplayHeight(0); + + ALOGV("display is %ld x %ld\n", displayWidth, displayHeight); + + sp<SurfaceControl> control = + composerClient->createSurface( + String8("A Surface"), + 0, + displayWidth, + displayHeight, + PIXEL_FORMAT_RGB_565, + 0); + + CHECK(control != NULL); + CHECK(control->isValid()); + + SurfaceComposerClient::openGlobalTransaction(); + CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK); + CHECK_EQ(control->show(), (status_t)OK); + SurfaceComposerClient::closeGlobalTransaction(); + + sp<Surface> surface = control->getSurface(); + CHECK(surface != NULL); + + sp<SimplePlayer> player = new SimplePlayer; + looper->registerHandler(player); + + player->setDataSource(argv[0]); + player->setSurface(surface->getSurfaceTexture()); + player->start(); + sleep(10); + player->stop(); + player->reset(); + + composerClient->dispose(); + } else { + decode(looper, argv[0], useAudio, useVideo); + } + + looper->stop(); + + return 0; +} diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp index ae80f88..18e2532 100644 --- a/cmds/stagefright/sf2.cpp +++ b/cmds/stagefright/sf2.cpp @@ -198,9 +198,7 @@ protected: (new AMessage(kWhatSeek, id()))->post(5000000ll); } else if (what == ACodec::kWhatOutputFormatChanged) { - } else { - CHECK_EQ(what, (int32_t)ACodec::kWhatShutdownCompleted); - + } else if (what == ACodec::kWhatShutdownCompleted) { mDecodeLooper->unregisterHandler(mCodec->id()); if (mDecodeLooper != looper()) { |