summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/OMXDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/OMXDecoder.cpp')
-rw-r--r--media/libstagefright/OMXDecoder.cpp1329
1 files changed, 1329 insertions, 0 deletions
diff --git a/media/libstagefright/OMXDecoder.cpp b/media/libstagefright/OMXDecoder.cpp
new file mode 100644
index 0000000..c059a9d
--- /dev/null
+++ b/media/libstagefright/OMXDecoder.cpp
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (C) 2009 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 "OMXDecoder"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <OMX_Component.h>
+
+#include <media/stagefright/ESDS.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXDecoder.h>
+
+namespace android {
+
+class OMXMediaBuffer : public MediaBuffer {
+public:
+ OMXMediaBuffer(IOMX::buffer_id buffer_id, const sp<IMemory> &mem)
+ : MediaBuffer(mem->pointer(),
+ mem->size()),
+ mBufferID(buffer_id),
+ mMem(mem) {
+ }
+
+ IOMX::buffer_id buffer_id() const { return mBufferID; }
+
+private:
+ IOMX::buffer_id mBufferID;
+ sp<IMemory> mMem;
+
+ OMXMediaBuffer(const OMXMediaBuffer &);
+ OMXMediaBuffer &operator=(const OMXMediaBuffer &);
+};
+
+struct CodecInfo {
+ const char *mime;
+ const char *codec;
+};
+
+static const CodecInfo kDecoderInfo[] = {
+ { "audio/mpeg", "OMX.PV.mp3dec" },
+ { "audio/3gpp", "OMX.PV.amrdec" },
+ { "audio/mp4a-latm", "OMX.PV.aacdec" },
+ { "video/mp4v-es", "OMX.qcom.video.decoder.mpeg4" },
+ { "video/mp4v-es", "OMX.PV.mpeg4dec" },
+ { "video/3gpp", "OMX.qcom.video.decoder.h263" },
+ { "video/3gpp", "OMX.PV.h263dec" },
+ { "video/avc", "OMX.qcom.video.decoder.avc" },
+ { "video/avc", "OMX.PV.avcdec" },
+};
+
+static const CodecInfo kEncoderInfo[] = {
+ { "audio/3gpp", "OMX.PV.amrencnb" },
+ { "audio/mp4a-latm", "OMX.PV.aacenc" },
+ { "video/mp4v-es", "OMX.qcom.video.encoder.mpeg4" },
+ { "video/mp4v-es", "OMX.PV.mpeg4enc" },
+ { "video/3gpp", "OMX.qcom.video.encoder.h263" },
+ { "video/3gpp", "OMX.PV.h263enc" },
+ { "video/avc", "OMX.PV.avcenc" },
+};
+
+static const char *GetCodec(const CodecInfo *info, size_t numInfos,
+ const char *mime, int index) {
+ assert(index >= 0);
+ for(size_t i = 0; i < numInfos; ++i) {
+ if (!strcasecmp(mime, info[i].mime)) {
+ if (index == 0) {
+ return info[i].codec;
+ }
+
+ --index;
+ }
+ }
+
+ return NULL;
+}
+
+// static
+OMXDecoder *OMXDecoder::Create(OMXClient *client, const sp<MetaData> &meta) {
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ sp<IOMX> omx = client->interface();
+
+ const char *codec = NULL;
+ IOMX::node_id node = 0;
+ for (int index = 0;; ++index) {
+ codec = GetCodec(
+ kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
+ mime, index);
+
+ if (!codec) {
+ return NULL;
+ }
+
+ LOGI("Attempting to allocate OMX node '%s'", codec);
+
+ status_t err = omx->allocate_node(codec, &node);
+ if (err == OK) {
+ break;
+ }
+ }
+
+ OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec);
+
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (meta->findData(kKeyESDS, &type, &data, &size)) {
+ ESDS esds((const char *)data, size);
+ assert(esds.InitCheck() == OK);
+
+ const void *codec_specific_data;
+ size_t codec_specific_data_size;
+ esds.getCodecSpecificInfo(
+ &codec_specific_data, &codec_specific_data_size);
+
+ printf("found codec specific data of size %d\n",
+ codec_specific_data_size);
+
+ decoder->addCodecSpecificData(
+ codec_specific_data, codec_specific_data_size);
+ } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+ printf("found avcc of size %d\n", size);
+
+ const uint8_t *ptr = (const uint8_t *)data + 6;
+ size -= 6;
+ while (size >= 2) {
+ size_t length = ptr[0] << 8 | ptr[1];
+
+ ptr += 2;
+ size -= 2;
+
+ // printf("length = %d, size = %d\n", length, size);
+
+ assert(size >= length);
+
+ decoder->addCodecSpecificData(ptr, length);
+
+ ptr += length;
+ size -= length;
+
+ if (size <= 1) {
+ break;
+ }
+
+ ptr++; // XXX skip trailing 0x01 byte???
+ --size;
+ }
+ }
+
+ return decoder;
+}
+
+// static
+OMXDecoder *OMXDecoder::CreateEncoder(
+ OMXClient *client, const sp<MetaData> &meta) {
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ sp<IOMX> omx = client->interface();
+
+ const char *codec = NULL;
+ IOMX::node_id node = 0;
+ for (int index = 0;; ++index) {
+ codec = GetCodec(
+ kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
+ mime, index);
+
+ if (!codec) {
+ return NULL;
+ }
+
+ LOGI("Attempting to allocate OMX node '%s'", codec);
+
+ status_t err = omx->allocate_node(codec, &node);
+ if (err == OK) {
+ break;
+ }
+ }
+
+ OMXDecoder *encoder = new OMXDecoder(client, node, mime, codec);
+
+ return encoder;
+}
+
+OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node,
+ const char *mime, const char *codec)
+ : mClient(client),
+ mOMX(mClient->interface()),
+ mNode(node),
+ mComponentName(strdup(codec)),
+ mIsMP3(!strcasecmp(mime, "audio/mpeg")),
+ mSource(NULL),
+ mCodecSpecificDataIterator(mCodecSpecificData.begin()),
+ mState(OMX_StateLoaded),
+ mPortStatusMask(kPortStatusActive << 2 | kPortStatusActive),
+ mShutdownInitiated(false),
+ mDealer(new MemoryDealer(3 * 1024 * 1024)),
+ mSeeking(false),
+ mStarted(false),
+ mErrorCondition(OK),
+ mReachedEndOfInput(false) {
+ mClient->registerObserver(mNode, this);
+
+ mBuffers.push(); // input buffers
+ mBuffers.push(); // output buffers
+}
+
+OMXDecoder::~OMXDecoder() {
+ if (mStarted) {
+ stop();
+ }
+
+ for (List<CodecSpecificData>::iterator it = mCodecSpecificData.begin();
+ it != mCodecSpecificData.end(); ++it) {
+ free((*it).data);
+ }
+ mCodecSpecificData.clear();
+
+ mClient->unregisterObserver(mNode);
+
+ status_t err = mOMX->free_node(mNode);
+ assert(err == OK);
+ mNode = 0;
+
+ free(mComponentName);
+ mComponentName = NULL;
+}
+
+void OMXDecoder::setSource(MediaSource *source) {
+ Mutex::Autolock autoLock(mLock);
+
+ assert(mSource == NULL);
+
+ mSource = source;
+ setup();
+}
+
+status_t OMXDecoder::start(MetaData *) {
+ assert(!mStarted);
+
+ // mDealer->dump("Decoder Dealer");
+
+ sp<MetaData> params = new MetaData;
+ if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) {
+ params->setInt32(kKeyNeedsNALFraming, true);
+ }
+
+ status_t err = mSource->start(params.get());
+
+ if (err != OK) {
+ return err;
+ }
+
+ postStart();
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t OMXDecoder::stop() {
+ assert(mStarted);
+
+ LOGI("Initiating OMX Node shutdown, busy polling.");
+ initiateShutdown();
+
+ // Important: initiateShutdown must be called first, _then_ release
+ // buffers we're holding onto.
+ while (!mOutputBuffers.empty()) {
+ MediaBuffer *buffer = *mOutputBuffers.begin();
+ mOutputBuffers.erase(mOutputBuffers.begin());
+
+ LOGV("releasing buffer %p.", buffer->data());
+
+ buffer->release();
+ buffer = NULL;
+ }
+
+ int attempt = 1;
+ while (mState != OMX_StateLoaded && attempt < 10) {
+ usleep(100000);
+
+ ++attempt;
+ }
+
+ if (mState != OMX_StateLoaded) {
+ LOGE("!!! OMX Node '%s' did NOT shutdown cleanly !!!", mComponentName);
+ } else {
+ LOGI("OMX Node '%s' has shutdown cleanly.", mComponentName);
+ }
+
+ mSource->stop();
+
+ mCodecSpecificDataIterator = mCodecSpecificData.begin();
+ mShutdownInitiated = false;
+ mSeeking = false;
+ mStarted = false;
+ mErrorCondition = OK;
+ mReachedEndOfInput = false;
+
+ return OK;
+}
+
+sp<MetaData> OMXDecoder::getFormat() {
+ return mOutputFormat;
+}
+
+status_t OMXDecoder::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ assert(mStarted);
+
+ *out = NULL;
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (mErrorCondition != OK && mErrorCondition != ERROR_END_OF_STREAM) {
+ // Errors are sticky.
+ return mErrorCondition;
+ }
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ LOGI("[%s] seeking to %lld us", mComponentName, seekTimeUs);
+
+ mErrorCondition = OK;
+ mReachedEndOfInput = false;
+
+ setPortStatus(kPortIndexInput, kPortStatusFlushing);
+ setPortStatus(kPortIndexOutput, kPortStatusFlushing);
+
+ mSeeking = true;
+ mSeekTimeUs = seekTimeUs;
+
+ while (!mOutputBuffers.empty()) {
+ OMXMediaBuffer *buffer =
+ static_cast<OMXMediaBuffer *>(*mOutputBuffers.begin());
+
+ // We could have used buffer->release() instead, but we're
+ // holding the lock and signalBufferReturned attempts to acquire
+ // the lock.
+ buffer->claim();
+ mBuffers.editItemAt(
+ kPortIndexOutput).push_back(buffer->buffer_id());
+ buffer = NULL;
+
+ mOutputBuffers.erase(mOutputBuffers.begin());
+ }
+
+ status_t err = mOMX->send_command(mNode, OMX_CommandFlush, -1);
+ assert(err == OK);
+
+ // Once flushing is completed buffers will again be scheduled to be
+ // filled/emptied.
+ }
+
+ while (mErrorCondition == OK && mOutputBuffers.empty()) {
+ mOutputBufferAvailable.wait(mLock);
+ }
+
+ if (!mOutputBuffers.empty()) {
+ MediaBuffer *buffer = *mOutputBuffers.begin();
+ mOutputBuffers.erase(mOutputBuffers.begin());
+
+ *out = buffer;
+
+ return OK;
+ } else {
+ assert(mErrorCondition != OK);
+ return mErrorCondition;
+ }
+}
+
+void OMXDecoder::addCodecSpecificData(const void *data, size_t size) {
+ CodecSpecificData specific;
+ specific.data = malloc(size);
+ memcpy(specific.data, data, size);
+ specific.size = size;
+
+ mCodecSpecificData.push_back(specific);
+ mCodecSpecificDataIterator = mCodecSpecificData.begin();
+}
+
+void OMXDecoder::onOMXMessage(const omx_message &msg) {
+ Mutex::Autolock autoLock(mLock);
+
+ switch (msg.type) {
+ case omx_message::START:
+ {
+ onStart();
+ break;
+ }
+
+ case omx_message::EVENT:
+ {
+ onEvent(msg.u.event_data.event, msg.u.event_data.data1,
+ msg.u.event_data.data2);
+ break;
+ }
+
+ case omx_message::EMPTY_BUFFER_DONE:
+ {
+ onEmptyBufferDone(msg.u.buffer_data.buffer);
+ break;
+ }
+
+ case omx_message::FILL_BUFFER_DONE:
+ case omx_message::INITIAL_FILL_BUFFER:
+ {
+ onFillBufferDone(msg);
+ break;
+ }
+
+ default:
+ LOGE("received unknown omx_message type %d", msg.type);
+ break;
+ }
+}
+
+void OMXDecoder::setAMRFormat() {
+ OMX_AUDIO_PARAM_AMRTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexInput;
+
+ status_t err =
+ mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+
+ assert(err == NO_ERROR);
+
+ def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+ def.eAMRBandMode = OMX_AUDIO_AMRBandModeNB0;
+
+ err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setAACFormat() {
+ OMX_AUDIO_PARAM_AACPROFILETYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexInput;
+
+ status_t err =
+ mOMX->get_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ def.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
+
+ err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setVideoOutputFormat(OMX_U32 width, OMX_U32 height) {
+ LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ bool is_encoder = strstr(mComponentName, ".encoder.") != NULL; // XXX
+
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = is_encoder ? kPortIndexOutput : kPortIndexInput;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ assert(err == NO_ERROR);
+
+#if 1
+ // XXX Need a (much) better heuristic to compute input buffer sizes.
+ const size_t X = 64 * 1024;
+ if (def.nBufferSize < X) {
+ def.nBufferSize = X;
+ }
+#endif
+
+ assert(def.eDomain == OMX_PortDomainVideo);
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ // video_def.eCompressionFormat = OMX_VIDEO_CodingAVC;
+ video_def->eColorFormat = OMX_COLOR_FormatUnused;
+
+ err = mOMX->set_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = is_encoder ? kPortIndexInput : kPortIndexOutput;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ assert(def.eDomain == OMX_PortDomainVideo);
+
+ def.nBufferSize =
+ (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2; // YUV420
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ video_def->nStride = width;
+ // video_def->nSliceHeight = height;
+ video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
+// video_def->eColorFormat = OMX_COLOR_FormatYUV420Planar;
+
+ err = mOMX->set_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setup() {
+ const sp<MetaData> &meta = mSource->getFormat();
+
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ if (!strcasecmp(mime, "audio/3gpp")) {
+ setAMRFormat();
+ } else if (!strcasecmp(mime, "audio/mp4a-latm")) {
+ setAACFormat();
+ } else if (!strncasecmp(mime, "video/", 6)) {
+ int32_t width, height;
+ bool success = meta->findInt32(kKeyWidth, &width);
+ success = success && meta->findInt32(kKeyHeight, &height);
+ assert(success);
+
+ setVideoOutputFormat(width, height);
+ }
+
+ // dumpPortDefinition(0);
+ // dumpPortDefinition(1);
+
+ mOutputFormat = new MetaData;
+ mOutputFormat->setCString(kKeyDecoderComponent, mComponentName);
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ switch (def.eDomain) {
+ case OMX_PortDomainAudio:
+ {
+ OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio;
+
+ assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM);
+
+ OMX_AUDIO_PARAM_PCMMODETYPE params;
+ params.nSize = sizeof(params);
+ params.nVersion.s.nVersionMajor = 1;
+ params.nVersion.s.nVersionMinor = 1;
+ params.nPortIndex = kPortIndexOutput;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamAudioPcm, &params, sizeof(params));
+ assert(err == OK);
+
+ assert(params.eNumData == OMX_NumericalDataSigned);
+ assert(params.nBitPerSample == 16);
+ assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear);
+
+ int32_t numChannels, sampleRate;
+ meta->findInt32(kKeyChannelCount, &numChannels);
+ meta->findInt32(kKeySampleRate, &sampleRate);
+
+ mOutputFormat->setCString(kKeyMIMEType, "audio/raw");
+ mOutputFormat->setInt32(kKeyChannelCount, numChannels);
+ mOutputFormat->setInt32(kKeySampleRate, sampleRate);
+ break;
+ }
+
+ case OMX_PortDomainVideo:
+ {
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ if (video_def->eCompressionFormat == OMX_VIDEO_CodingUnused) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/raw");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingMPEG4) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/mp4v-es");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingH263) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/3gpp");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingAVC) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/avc");
+ } else {
+ assert(!"Unknown compression format.");
+ }
+
+ if (!strcmp(mComponentName, "OMX.PV.avcdec")) {
+ // This component appears to be lying to me.
+ mOutputFormat->setInt32(
+ kKeyWidth, (video_def->nFrameWidth + 15) & -16);
+ mOutputFormat->setInt32(
+ kKeyHeight, (video_def->nFrameHeight + 15) & -16);
+ } else {
+ mOutputFormat->setInt32(kKeyWidth, video_def->nFrameWidth);
+ mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight);
+ }
+
+ mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat);
+ break;
+ }
+
+ default:
+ {
+ assert(!"should not be here, neither audio nor video.");
+ break;
+ }
+ }
+}
+
+void OMXDecoder::onStart() {
+ bool needs_qcom_hack =
+ !strncmp(mComponentName, "OMX.qcom.video.", 15);
+
+ if (!needs_qcom_hack) {
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+ }
+
+ allocateBuffers(kPortIndexInput);
+ allocateBuffers(kPortIndexOutput);
+
+ if (needs_qcom_hack) {
+ // XXX this should happen before AllocateBuffers, but qcom's
+ // h264 vdec disagrees.
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+ }
+}
+
+void OMXDecoder::allocateBuffers(OMX_U32 port_index) {
+ assert(mBuffers[port_index].empty());
+
+ OMX_U32 num_buffers;
+ OMX_U32 buffer_size;
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nVersion.s.nRevision = 0;
+ def.nVersion.s.nStep = 0;
+ def.nPortIndex = port_index;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ num_buffers = def.nBufferCountActual;
+ buffer_size = def.nBufferSize;
+
+ LOGV("[%s] port %ld: allocating %ld buffers of size %ld each\n",
+ mComponentName, port_index, num_buffers, buffer_size);
+
+ for (OMX_U32 i = 0; i < num_buffers; ++i) {
+ sp<IMemory> mem = mDealer->allocate(buffer_size);
+ assert(mem.get() != NULL);
+
+ IOMX::buffer_id buffer;
+ status_t err;
+
+ if (port_index == kPortIndexInput
+ && !strncmp(mComponentName, "OMX.qcom.video.encoder.", 23)) {
+ // qcom's H.263 encoder appears to want to allocate its own input
+ // buffers.
+ err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer);
+ if (err != OK) {
+ LOGE("[%s] allocate_buffer_with_backup failed with error %d",
+ mComponentName, err);
+ }
+ } else if (port_index == kPortIndexOutput
+ && !strncmp(mComponentName, "OMX.qcom.video.decoder.", 23)) {
+#if 1
+ err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer);
+#else
+ // XXX This is fine as long as we are either running the player
+ // inside the media server process or we are using the
+ // QComHardwareRenderer to output the frames.
+ err = mOMX->allocate_buffer(mNode, port_index, buffer_size, &buffer);
+#endif
+ if (err != OK) {
+ LOGE("[%s] allocate_buffer_with_backup failed with error %d",
+ mComponentName, err);
+ }
+ } else {
+ err = mOMX->use_buffer(mNode, port_index, mem, &buffer);
+ if (err != OK) {
+ LOGE("[%s] use_buffer failed with error %d",
+ mComponentName, err);
+ }
+ }
+ assert(err == OK);
+
+ LOGV("allocated %s buffer %p.",
+ port_index == kPortIndexInput ? "INPUT" : "OUTPUT",
+ buffer);
+
+ mBuffers.editItemAt(port_index).push_back(buffer);
+ mBufferMap.add(buffer, mem);
+
+ if (port_index == kPortIndexOutput) {
+ OMXMediaBuffer *media_buffer = new OMXMediaBuffer(buffer, mem);
+ media_buffer->setObserver(this);
+
+ mMediaBufferMap.add(buffer, media_buffer);
+ }
+ }
+
+ LOGV("allocate %s buffers done.",
+ port_index == kPortIndexInput ? "INPUT" : "OUTPUT");
+}
+
+void OMXDecoder::onEvent(
+ OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
+ LOGV("[%s] onEvent event=%d, data1=%ld, data2=%ld",
+ mComponentName, event, data1, data2);
+
+ switch (event) {
+ case OMX_EventCmdComplete: {
+ onEventCmdComplete(
+ static_cast<OMX_COMMANDTYPE>(data1), data2);
+
+ break;
+ }
+
+ case OMX_EventPortSettingsChanged: {
+ onEventPortSettingsChanged(data1);
+ break;
+ }
+
+ case OMX_EventBufferFlag: {
+ // initiateShutdown();
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void OMXDecoder::onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data) {
+ switch (type) {
+ case OMX_CommandStateSet: {
+ OMX_STATETYPE state = static_cast<OMX_STATETYPE>(data);
+ onStateChanged(state);
+ break;
+ }
+
+ case OMX_CommandPortDisable: {
+ OMX_U32 port_index = data;
+ assert(getPortStatus(port_index) == kPortStatusDisabled);
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandPortEnable, port_index);
+
+ allocateBuffers(port_index);
+
+ break;
+ }
+
+ case OMX_CommandPortEnable: {
+ OMX_U32 port_index = data;
+ assert(getPortStatus(port_index) ==kPortStatusDisabled);
+ setPortStatus(port_index, kPortStatusActive);
+
+ assert(port_index == kPortIndexOutput);
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ while (!obuffers->empty()) {
+ IOMX::buffer_id buffer = *obuffers->begin();
+ obuffers->erase(obuffers->begin());
+
+ status_t err = mClient->fillBuffer(mNode, buffer);
+ assert(err == NO_ERROR);
+ }
+
+ break;
+ }
+
+ case OMX_CommandFlush: {
+ OMX_U32 port_index = data;
+ LOGV("Port %ld flush complete.", port_index);
+ assert(getPortStatus(port_index) == kPortStatusFlushing);
+
+ setPortStatus(port_index, kPortStatusActive);
+ BufferList *buffers = &mBuffers.editItemAt(port_index);
+ while (!buffers->empty()) {
+ IOMX::buffer_id buffer = *buffers->begin();
+ buffers->erase(buffers->begin());
+
+ if (port_index == kPortIndexInput) {
+ postEmptyBufferDone(buffer);
+ } else {
+ postInitialFillBuffer(buffer);
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void OMXDecoder::onEventPortSettingsChanged(OMX_U32 port_index) {
+ assert(getPortStatus(port_index) == kPortStatusActive);
+ setPortStatus(port_index, kPortStatusDisabled);
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandPortDisable, port_index);
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onStateChanged(OMX_STATETYPE to) {
+ if (mState == OMX_StateLoaded) {
+ assert(to == OMX_StateIdle);
+
+ mState = to;
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateExecuting);
+ assert(err == NO_ERROR);
+ } else if (mState == OMX_StateIdle) {
+ if (to == OMX_StateExecuting) {
+ mState = to;
+
+ BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput);
+ while (!ibuffers->empty()) {
+ IOMX::buffer_id buffer = *ibuffers->begin();
+ ibuffers->erase(ibuffers->begin());
+
+ postEmptyBufferDone(buffer);
+ }
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ while (!obuffers->empty()) {
+ IOMX::buffer_id buffer = *obuffers->begin();
+ obuffers->erase(obuffers->begin());
+
+ postInitialFillBuffer(buffer);
+ }
+ } else {
+ assert(to == OMX_StateLoaded);
+
+ mState = to;
+
+ setPortStatus(kPortIndexInput, kPortStatusActive);
+ setPortStatus(kPortIndexOutput, kPortStatusActive);
+ }
+ } else if (mState == OMX_StateExecuting) {
+ assert(to == OMX_StateIdle);
+
+ mState = to;
+
+ LOGV("Executing->Idle complete, initiating Idle->Loaded");
+ status_t err =
+ mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateLoaded);
+ assert(err == NO_ERROR);
+
+ BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput);
+ for (BufferList::iterator it = ibuffers->begin();
+ it != ibuffers->end(); ++it) {
+ freeInputBuffer(*it);
+ }
+ ibuffers->clear();
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ for (BufferList::iterator it = obuffers->begin();
+ it != obuffers->end(); ++it) {
+ freeOutputBuffer(*it);
+ }
+ obuffers->clear();
+ }
+}
+
+void OMXDecoder::initiateShutdown() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mShutdownInitiated) {
+ return;
+ }
+
+ if (mState == OMX_StateLoaded) {
+ return;
+ }
+
+ assert(mState == OMX_StateExecuting);
+
+ mShutdownInitiated = true;
+
+ status_t err =
+ mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+
+ setPortStatus(kPortIndexInput, kPortStatusShutdown);
+ setPortStatus(kPortIndexOutput, kPortStatusShutdown);
+}
+
+void OMXDecoder::setPortStatus(OMX_U32 port_index, PortStatus status) {
+ int shift = 2 * port_index;
+
+ mPortStatusMask &= ~(3 << shift);
+ mPortStatusMask |= status << shift;
+}
+
+OMXDecoder::PortStatus OMXDecoder::getPortStatus(
+ OMX_U32 port_index) const {
+ int shift = 2 * port_index;
+
+ return static_cast<PortStatus>((mPortStatusMask >> shift) & 3);
+}
+
+void OMXDecoder::onEmptyBufferDone(IOMX::buffer_id buffer) {
+ LOGV("[%s] onEmptyBufferDone (%p)", mComponentName, buffer);
+
+ status_t err;
+ switch (getPortStatus(kPortIndexInput)) {
+ case kPortStatusDisabled:
+ freeInputBuffer(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusShutdown:
+ LOGV("We're shutting down, enqueue INPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusFlushing:
+ LOGV("We're currently flushing, enqueue INPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ default:
+ onRealEmptyBufferDone(buffer);
+ err = NO_ERROR;
+ break;
+ }
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onFillBufferDone(const omx_message &msg) {
+ IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer;
+
+ LOGV("[%s] onFillBufferDone (%p)", mComponentName, buffer);
+
+ status_t err;
+ switch (getPortStatus(kPortIndexOutput)) {
+ case kPortStatusDisabled:
+ freeOutputBuffer(buffer);
+ err = NO_ERROR;
+ break;
+ case kPortStatusShutdown:
+ LOGV("We're shutting down, enqueue OUTPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusFlushing:
+ LOGV("We're currently flushing, enqueue OUTPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ default:
+ {
+ if (msg.type == omx_message::INITIAL_FILL_BUFFER) {
+ err = mClient->fillBuffer(mNode, buffer);
+ } else {
+ LOGV("[%s] Filled OUTPUT buffer %p, flags=0x%08lx.",
+ mComponentName, buffer, msg.u.extended_buffer_data.flags);
+
+ onRealFillBufferDone(msg);
+ err = NO_ERROR;
+ }
+ break;
+ }
+ }
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onRealEmptyBufferDone(IOMX::buffer_id buffer) {
+ if (mReachedEndOfInput) {
+ // We already sent the EOS notification.
+
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ return;
+ }
+
+ const sp<IMemory> mem = mBufferMap.valueFor(buffer);
+ assert(mem.get() != NULL);
+
+ static const uint8_t kNALStartCode[4] = { 0x00, 0x00, 0x00, 0x01 };
+
+ if (mCodecSpecificDataIterator != mCodecSpecificData.end()) {
+ List<CodecSpecificData>::iterator it = mCodecSpecificDataIterator;
+
+ size_t range_length = 0;
+
+ if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) {
+ assert((*mCodecSpecificDataIterator).size + 4 <= mem->size());
+
+ memcpy(mem->pointer(), kNALStartCode, 4);
+
+ memcpy((uint8_t *)mem->pointer() + 4, (*it).data, (*it).size);
+ range_length = (*it).size + 4;
+ } else {
+ assert((*mCodecSpecificDataIterator).size <= mem->size());
+
+ memcpy((uint8_t *)mem->pointer(), (*it).data, (*it).size);
+ range_length = (*it).size;
+ }
+
+ ++mCodecSpecificDataIterator;
+
+ status_t err = mClient->emptyBuffer(
+ mNode, buffer, 0, range_length,
+ OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
+ 0);
+
+ assert(err == NO_ERROR);
+
+ return;
+ }
+
+ LOGV("[%s] waiting for input data", mComponentName);
+
+ MediaBuffer *input_buffer;
+ for (;;) {
+ status_t err;
+
+ if (mSeeking) {
+ MediaSource::ReadOptions options;
+ options.setSeekTo(mSeekTimeUs);
+
+ mSeeking = false;
+
+ err = mSource->read(&input_buffer, &options);
+ } else {
+ err = mSource->read(&input_buffer);
+ }
+ assert((err == OK && input_buffer != NULL)
+ || (err != OK && input_buffer == NULL));
+
+ if (err == ERROR_END_OF_STREAM) {
+ LOGE("[%s] Reached end of stream.", mComponentName);
+ mReachedEndOfInput = true;
+ } else {
+ LOGV("[%s] got input data", mComponentName);
+ }
+
+ if (err != OK) {
+ status_t err2 = mClient->emptyBuffer(
+ mNode, buffer, 0, 0, OMX_BUFFERFLAG_EOS, 0);
+
+ assert(err2 == NO_ERROR);
+ return;
+ }
+
+ if (mSeeking) {
+ input_buffer->release();
+ input_buffer = NULL;
+
+ continue;
+ }
+
+ break;
+ }
+
+ const uint8_t *src_data =
+ (const uint8_t *)input_buffer->data() + input_buffer->range_offset();
+
+ size_t src_length = input_buffer->range_length();
+ if (src_length == 195840) {
+ // When feeding the output of the AVC decoder into the H263 encoder,
+ // buffer sizes mismatch if width % 16 != 0 || height % 16 != 0.
+ src_length = 194400; // XXX HACK
+ } else if (src_length == 115200) {
+ src_length = 114240; // XXX HACK
+ }
+
+ if (src_length > mem->size()) {
+ LOGE("src_length=%d > mem->size() = %d\n",
+ src_length, mem->size());
+ }
+
+ assert(src_length <= mem->size());
+ memcpy(mem->pointer(), src_data, src_length);
+
+ OMX_U32 flags = 0;
+ if (!mIsMP3) {
+ // Only mp3 audio data may be streamed, all other data is assumed
+ // to be fed into the decoder at frame boundaries.
+ flags |= OMX_BUFFERFLAG_ENDOFFRAME;
+ }
+
+ int32_t units, scale;
+ bool success =
+ input_buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
+
+ success = success &&
+ input_buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
+
+ OMX_TICKS timestamp = 0;
+
+ if (success) {
+ // XXX units should be microseconds but PV treats them as milliseconds.
+ timestamp = ((OMX_S64)units * 1000) / scale;
+ }
+
+ input_buffer->release();
+ input_buffer = NULL;
+
+ LOGV("[%s] Calling EmptyBuffer on buffer %p",
+ mComponentName, buffer);
+
+ status_t err2 = mClient->emptyBuffer(
+ mNode, buffer, 0, src_length, flags, timestamp);
+ assert(err2 == OK);
+}
+
+void OMXDecoder::onRealFillBufferDone(const omx_message &msg) {
+ OMXMediaBuffer *media_buffer =
+ mMediaBufferMap.valueFor(msg.u.extended_buffer_data.buffer);
+
+ media_buffer->set_range(
+ msg.u.extended_buffer_data.range_offset,
+ msg.u.extended_buffer_data.range_length);
+
+ media_buffer->add_ref();
+
+ media_buffer->meta_data()->clear();
+
+ media_buffer->meta_data()->setInt32(
+ kKeyTimeUnits, msg.u.extended_buffer_data.timestamp);
+ media_buffer->meta_data()->setInt32(kKeyTimeScale, 1000);
+
+ if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) {
+ media_buffer->meta_data()->setInt32(kKeyIsSyncFrame, true);
+ }
+
+ media_buffer->meta_data()->setPointer(
+ kKeyPlatformPrivate,
+ msg.u.extended_buffer_data.platform_private);
+
+ if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) {
+ mErrorCondition = ERROR_END_OF_STREAM;
+ }
+
+ mOutputBuffers.push_back(media_buffer);
+ mOutputBufferAvailable.signal();
+}
+
+void OMXDecoder::signalBufferReturned(MediaBuffer *_buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ OMXMediaBuffer *media_buffer = static_cast<OMXMediaBuffer *>(_buffer);
+
+ IOMX::buffer_id buffer = media_buffer->buffer_id();
+
+ PortStatus outputStatus = getPortStatus(kPortIndexOutput);
+ if (outputStatus == kPortStatusShutdown
+ || outputStatus == kPortStatusFlushing) {
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ } else {
+ LOGV("[%s] Calling FillBuffer on buffer %p.", mComponentName, buffer);
+
+ status_t err = mClient->fillBuffer(mNode, buffer);
+ assert(err == NO_ERROR);
+ }
+}
+
+void OMXDecoder::freeInputBuffer(IOMX::buffer_id buffer) {
+ LOGV("freeInputBuffer %p", buffer);
+
+ status_t err = mOMX->free_buffer(mNode, kPortIndexInput, buffer);
+ assert(err == NO_ERROR);
+ mBufferMap.removeItem(buffer);
+
+ LOGV("freeInputBuffer %p done", buffer);
+}
+
+void OMXDecoder::freeOutputBuffer(IOMX::buffer_id buffer) {
+ LOGV("freeOutputBuffer %p", buffer);
+
+ status_t err = mOMX->free_buffer(mNode, kPortIndexOutput, buffer);
+ assert(err == NO_ERROR);
+ mBufferMap.removeItem(buffer);
+
+ ssize_t index = mMediaBufferMap.indexOfKey(buffer);
+ assert(index >= 0);
+ MediaBuffer *mbuffer = mMediaBufferMap.editValueAt(index);
+ mMediaBufferMap.removeItemsAt(index);
+ mbuffer->setObserver(NULL);
+ mbuffer->release();
+ mbuffer = NULL;
+
+ LOGV("freeOutputBuffer %p done", buffer);
+}
+
+void OMXDecoder::dumpPortDefinition(OMX_U32 port_index) {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = port_index;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ LOGI("DumpPortDefinition on port %ld", port_index);
+ LOGI("nBufferCountActual = %ld, nBufferCountMin = %ld, nBufferSize = %ld",
+ def.nBufferCountActual, def.nBufferCountMin, def.nBufferSize);
+ switch (def.eDomain) {
+ case OMX_PortDomainAudio:
+ {
+ LOGI("eDomain = AUDIO");
+
+ if (port_index == kPortIndexOutput) {
+ OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio;
+ assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM);
+
+ OMX_AUDIO_PARAM_PCMMODETYPE params;
+ params.nSize = sizeof(params);
+ params.nVersion.s.nVersionMajor = 1;
+ params.nVersion.s.nVersionMinor = 1;
+ params.nPortIndex = port_index;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamAudioPcm, &params, sizeof(params));
+ assert(err == OK);
+
+ assert(params.nChannels == 1 || params.bInterleaved);
+ assert(params.eNumData == OMX_NumericalDataSigned);
+ assert(params.nBitPerSample == 16);
+ assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear);
+
+ LOGI("nChannels = %ld, nSamplingRate = %ld",
+ params.nChannels, params.nSamplingRate);
+ }
+
+ break;
+ }
+
+ case OMX_PortDomainVideo:
+ {
+ LOGI("eDomain = VIDEO");
+
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+ LOGI("nFrameWidth = %ld, nFrameHeight = %ld, nStride = %ld, "
+ "nSliceHeight = %ld",
+ video_def->nFrameWidth, video_def->nFrameHeight,
+ video_def->nStride, video_def->nSliceHeight);
+ LOGI("nBitrate = %ld, xFrameRate = %.2f",
+ video_def->nBitrate, video_def->xFramerate / 65536.0f);
+ LOGI("eCompressionFormat = %d, eColorFormat = %d",
+ video_def->eCompressionFormat, video_def->eColorFormat);
+
+ break;
+ }
+
+ default:
+ LOGI("eDomain = UNKNOWN");
+ break;
+ }
+}
+
+void OMXDecoder::postStart() {
+ omx_message msg;
+ msg.type = omx_message::START;
+ postMessage(msg);
+}
+
+void OMXDecoder::postEmptyBufferDone(IOMX::buffer_id buffer) {
+ omx_message msg;
+ msg.type = omx_message::EMPTY_BUFFER_DONE;
+ msg.u.buffer_data.node = mNode;
+ msg.u.buffer_data.buffer = buffer;
+ postMessage(msg);
+}
+
+void OMXDecoder::postInitialFillBuffer(IOMX::buffer_id buffer) {
+ omx_message msg;
+ msg.type = omx_message::INITIAL_FILL_BUFFER;
+ msg.u.buffer_data.node = mNode;
+ msg.u.buffer_data.buffer = buffer;
+ postMessage(msg);
+}
+
+} // namespace android