| /* |
| * 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 <ctype.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.TI.MP3.decode" }, |
| { "audio/mpeg", "OMX.PV.mp3dec" }, |
| { "audio/3gpp", "OMX.TI.AMR.decode" }, |
| { "audio/3gpp", "OMX.PV.amrdec" }, |
| { "audio/mp4a-latm", "OMX.TI.AAC.decode" }, |
| { "audio/mp4a-latm", "OMX.PV.aacdec" }, |
| { "video/mp4v-es", "OMX.qcom.video.decoder.mpeg4" }, |
| { "video/mp4v-es", "OMX.TI.Video.Decoder" }, |
| { "video/mp4v-es", "OMX.PV.mpeg4dec" }, |
| { "video/3gpp", "OMX.qcom.video.decoder.h263" }, |
| { "video/3gpp", "OMX.TI.Video.Decoder" }, |
| { "video/3gpp", "OMX.PV.h263dec" }, |
| { "video/avc", "OMX.qcom.video.decoder.avc" }, |
| { "video/avc", "OMX.TI.Video.Decoder" }, |
| { "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, |
| bool createEncoder) { |
| 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) { |
| if (createEncoder) { |
| codec = GetCodec( |
| kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), |
| mime, index); |
| } else { |
| 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; |
| } |
| } |
| |
| uint32_t quirks = 0; |
| if (!strcmp(codec, "OMX.PV.avcdec")) { |
| quirks |= kWantsRawNALFrames; |
| } |
| if (!strcmp(codec, "OMX.TI.AAC.decode") |
| || !strcmp(codec, "OMX.TI.MP3.decode")) { |
| quirks |= kDoesntReturnBuffersOnDisable; |
| } |
| if (!strcmp(codec, "OMX.TI.AAC.decode")) { |
| quirks |= kDoesntFlushOnExecutingToIdle; |
| quirks |= kDoesntProperlyFlushAllPortsAtOnce; |
| } |
| if (!strncmp(codec, "OMX.qcom.video.encoder.", 23)) { |
| quirks |= kRequiresAllocateBufferOnInputPorts; |
| } |
| if (!strncmp(codec, "OMX.qcom.video.decoder.", 23)) { |
| quirks |= kRequiresAllocateBufferOnOutputPorts; |
| } |
| if (!strncmp(codec, "OMX.qcom.video.", 15)) { |
| quirks |= kRequiresLoadedToIdleAfterAllocation; |
| } |
| if (!strcmp(codec, "OMX.TI.MP3.decode")) { |
| quirks |= kMeasuresTimeInMilliseconds; |
| } |
| |
| OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec, quirks); |
| |
| 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; |
| } |
| |
| OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node, |
| const char *mime, const char *codec, |
| uint32_t quirks) |
| : mClient(client), |
| mOMX(mClient->interface()), |
| mNode(node), |
| mComponentName(strdup(codec)), |
| mIsMP3(!strcasecmp(mime, "audio/mpeg")), |
| mIsAVC(!strcasecmp(mime, "video/avc")), |
| mQuirks(quirks), |
| mSource(NULL), |
| mCodecSpecificDataIterator(mCodecSpecificData.begin()), |
| mState(OMX_StateLoaded), |
| mPortStatusMask(kPortStatusActive << 2 | kPortStatusActive), |
| mShutdownInitiated(false), |
| mDealer(new MemoryDealer(5 * 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 (mIsAVC && !(mQuirks & kWantsRawNALFrames)) { |
| 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 < 20) { |
| 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()); |
| } |
| |
| // XXX One of TI's decoders appears to ignore a flush if it doesn't |
| // currently hold on to any buffers on the port in question and |
| // never sends the completion event... FIXME |
| |
| status_t err = mOMX->send_command(mNode, OMX_CommandFlush, OMX_ALL); |
| 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); |
| } |
| |
| status_t OMXDecoder::setVideoPortFormatType( |
| OMX_U32 portIndex, |
| OMX_VIDEO_CODINGTYPE compressionFormat, |
| OMX_COLOR_FORMATTYPE colorFormat) { |
| OMX_VIDEO_PARAM_PORTFORMATTYPE format; |
| format.nSize = sizeof(format); |
| format.nVersion.s.nVersionMajor = 1; |
| format.nVersion.s.nVersionMinor = 1; |
| format.nPortIndex = portIndex; |
| format.nIndex = 0; |
| bool found = false; |
| |
| OMX_U32 index = 0; |
| for (;;) { |
| format.nIndex = index; |
| status_t err = mOMX->get_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| |
| if (err != OK) { |
| return err; |
| } |
| |
| // The following assertion is violated by TI's video decoder. |
| // assert(format.nIndex == index); |
| |
| if (format.eCompressionFormat == compressionFormat |
| && format.eColorFormat == colorFormat) { |
| found = true; |
| break; |
| } |
| |
| ++index; |
| } |
| |
| if (!found) { |
| return UNKNOWN_ERROR; |
| } |
| |
| status_t err = mOMX->set_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| |
| return err; |
| } |
| |
| #if 1 |
| void OMXDecoder::setVideoOutputFormat( |
| const char *mime, OMX_U32 width, OMX_U32 height) { |
| LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height); |
| |
| #if 1 |
| // Enabling this code appears to be the right thing(tm), but,... |
| // the TI decoder then loses the ability to output YUV420 and only outputs |
| // YCbYCr (16bit) |
| if (!strcasecmp("video/avc", mime)) { |
| OMX_PARAM_COMPONENTROLETYPE role; |
| role.nSize = sizeof(role); |
| role.nVersion.s.nVersionMajor = 1; |
| role.nVersion.s.nVersionMinor = 1; |
| strncpy((char *)role.cRole, "video_decoder.avc", |
| OMX_MAX_STRINGNAME_SIZE - 1); |
| role.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; |
| |
| status_t err = mOMX->set_parameter( |
| mNode, OMX_IndexParamStandardComponentRole, |
| &role, sizeof(role)); |
| assert(err == OK); |
| } |
| #endif |
| |
| OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused; |
| if (!strcasecmp("video/avc", mime)) { |
| compressionFormat = OMX_VIDEO_CodingAVC; |
| } else if (!strcasecmp("video/mp4v-es", mime)) { |
| compressionFormat = OMX_VIDEO_CodingMPEG4; |
| } else if (!strcasecmp("video/3gpp", mime)) { |
| compressionFormat = OMX_VIDEO_CodingH263; |
| } else { |
| assert(!"Should not be here. Not a supported video mime type."); |
| } |
| |
| setVideoPortFormatType( |
| kPortIndexInput, compressionFormat, OMX_COLOR_FormatUnused); |
| |
| #if 1 |
| { |
| OMX_VIDEO_PARAM_PORTFORMATTYPE format; |
| format.nSize = sizeof(format); |
| format.nVersion.s.nVersionMajor = 1; |
| format.nVersion.s.nVersionMinor = 1; |
| format.nPortIndex = kPortIndexOutput; |
| format.nIndex = 0; |
| |
| status_t err = mOMX->get_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| assert(err == OK); |
| |
| assert(format.eCompressionFormat == OMX_VIDEO_CodingUnused); |
| |
| static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; |
| |
| assert(format.eColorFormat == OMX_COLOR_FormatYUV420Planar |
| || format.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar |
| || format.eColorFormat == OMX_COLOR_FormatCbYCrY |
| || format.eColorFormat == OMX_QCOM_COLOR_FormatYVU420SemiPlanar); |
| |
| err = mOMX->set_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| assert(err == OK); |
| } |
| #endif |
| |
| 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->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); |
| |
| #if 0 |
| def.nBufferSize = |
| (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2; // YUV420 |
| #endif |
| |
| video_def->nFrameWidth = width; |
| video_def->nFrameHeight = height; |
| |
| err = mOMX->set_parameter( |
| mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); |
| assert(err == NO_ERROR); |
| } |
| |
| #else |
| static void hexdump(const void *_data, size_t size) { |
| char line[256]; |
| char tmp[16]; |
| |
| const uint8_t *data = (const uint8_t *)_data; |
| size_t offset = 0; |
| while (offset < size) { |
| sprintf(line, "0x%04x ", offset); |
| |
| size_t n = size - offset; |
| if (n > 16) { |
| n = 16; |
| } |
| |
| for (size_t i = 0; i < 16; ++i) { |
| if (i == 8) { |
| strcat(line, " "); |
| } |
| |
| if (offset + i < size) { |
| sprintf(tmp, "%02x ", data[offset + i]); |
| strcat(line, tmp); |
| } else { |
| strcat(line, " "); |
| } |
| } |
| |
| strcat(line, " "); |
| |
| for (size_t i = 0; i < n; ++i) { |
| if (isprint(data[offset + i])) { |
| sprintf(tmp, "%c", data[offset + i]); |
| strcat(line, tmp); |
| } else { |
| strcat(line, "."); |
| } |
| } |
| |
| LOGI(line); |
| |
| offset += 16; |
| } |
| } |
| |
| static void DumpPortDefinitionType(const void *_param) { |
| OMX_PARAM_PORTDEFINITIONTYPE *param = (OMX_PARAM_PORTDEFINITIONTYPE *)_param; |
| |
| LOGI("nPortIndex=%ld eDir=%s nBufferCountActual=%ld nBufferCountMin=%ld nBufferSize=%ld", param->nPortIndex, param->eDir == OMX_DirInput ? "input" : "output", |
| param->nBufferCountActual, param->nBufferCountMin, param->nBufferSize); |
| |
| if (param->eDomain == OMX_PortDomainVideo) { |
| OMX_VIDEO_PORTDEFINITIONTYPE *video = ¶m->format.video; |
| LOGI("nFrameWidth=%ld nFrameHeight=%ld nStride=%ld nSliceHeight=%ld nBitrate=%ld xFramerate=%ld eCompressionFormat=%d eColorFormat=%d", |
| video->nFrameWidth, video->nFrameHeight, video->nStride, video->nSliceHeight, video->nBitrate, video->xFramerate, video->eCompressionFormat, video->eColorFormat); |
| } else { |
| hexdump(param, param->nSize); |
| } |
| } |
| |
| void OMXDecoder::setVideoOutputFormat( |
| const char *mime, OMX_U32 width, OMX_U32 height) { |
| LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height); |
| |
| #if 0 |
| // Enabling this code appears to be the right thing(tm), but,... |
| // the decoder then loses the ability to output YUV420 and only outputs |
| // YCbYCr (16bit) |
| { |
| OMX_PARAM_COMPONENTROLETYPE role; |
| role.nSize = sizeof(role); |
| role.nVersion.s.nVersionMajor = 1; |
| role.nVersion.s.nVersionMinor = 1; |
| strncpy((char *)role.cRole, "video_decoder.avc", |
| OMX_MAX_STRINGNAME_SIZE - 1); |
| role.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; |
| |
| status_t err = mOMX->set_parameter( |
| mNode, OMX_IndexParamStandardComponentRole, |
| &role, sizeof(role)); |
| assert(err == OK); |
| } |
| #endif |
| |
| setVideoPortFormatType( |
| kPortIndexInput, OMX_VIDEO_CodingAVC, OMX_COLOR_FormatUnused); |
| |
| #if 1 |
| { |
| OMX_VIDEO_PARAM_PORTFORMATTYPE format; |
| format.nSize = sizeof(format); |
| format.nVersion.s.nVersionMajor = 1; |
| format.nVersion.s.nVersionMinor = 1; |
| format.nPortIndex = kPortIndexOutput; |
| format.nIndex = 0; |
| |
| status_t err = mOMX->get_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| assert(err == OK); |
| |
| LOGI("XXX MyOMX_GetParameter OMX_IndexParamVideoPortFormat"); |
| hexdump(&format, format.nSize); |
| |
| assert(format.eCompressionFormat == OMX_VIDEO_CodingUnused); |
| assert(format.eColorFormat == OMX_COLOR_FormatYUV420Planar |
| || format.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar |
| || format.eColorFormat == OMX_COLOR_FormatCbYCrY); |
| |
| err = mOMX->set_parameter( |
| mNode, OMX_IndexParamVideoPortFormat, |
| &format, sizeof(format)); |
| assert(err == OK); |
| } |
| #endif |
| |
| OMX_PORT_PARAM_TYPE ptype; |
| ptype.nSize = sizeof(ptype); |
| ptype.nVersion.s.nVersionMajor = 1; |
| ptype.nVersion.s.nVersionMinor = 1; |
| |
| status_t err = mOMX->get_parameter( |
| mNode, OMX_IndexParamVideoInit, &ptype, sizeof(ptype)); |
| assert(err == OK); |
| |
| LOGI("XXX MyOMX_GetParameter OMX_IndexParamVideoInit"); |
| hexdump(&ptype, ptype.nSize); |
| |
| OMX_PARAM_PORTDEFINITIONTYPE def; |
| def.nSize = sizeof(def); |
| def.nVersion.s.nVersionMajor = 1; |
| def.nVersion.s.nVersionMinor = 1; |
| def.nPortIndex = kPortIndexInput; |
| |
| err = mOMX->get_parameter( |
| mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); |
| assert(err == OK); |
| |
| LOGI("XXX MyOMX_GetParameter OMX_IndexParamPortDefinition"); |
| DumpPortDefinitionType(&def); |
| |
| OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; |
| video_def->nFrameWidth = width; |
| video_def->nFrameHeight = height; |
| |
| err = mOMX->set_parameter( |
| mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); |
| assert(err == OK); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| |
| def.nPortIndex = kPortIndexOutput; |
| |
| err = mOMX->get_parameter( |
| mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); |
| assert(err == OK); |
| |
| LOGI("XXX MyOMX_GetParameter OMX_IndexParamPortDefinition"); |
| DumpPortDefinitionType(&def); |
| |
| video_def->nFrameWidth = width; |
| video_def->nFrameHeight = height; |
| |
| err = mOMX->set_parameter( |
| mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); |
| assert(err == OK); |
| } |
| |
| #endif |
| |
| 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(mime, 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, ¶ms, 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() { |
| if (!(mQuirks & kRequiresLoadedToIdleAfterAllocation)) { |
| status_t err = |
| mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); |
| assert(err == NO_ERROR); |
| } |
| |
| allocateBuffers(kPortIndexInput); |
| allocateBuffers(kPortIndexOutput); |
| |
| if (mQuirks & kRequiresLoadedToIdleAfterAllocation) { |
| // 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); |
| if (mem.get() == NULL) { |
| LOGE("[%s] allocating IMemory of size %ld FAILED.", |
| mComponentName, buffer_size); |
| } |
| assert(mem.get() != NULL); |
| |
| IOMX::buffer_id buffer; |
| status_t err; |
| |
| if (port_index == kPortIndexInput |
| && (mQuirks & kRequiresAllocateBufferOnInputPorts)) { |
| // 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 |
| && (mQuirks & kRequiresAllocateBufferOnOutputPorts)) { |
| #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); |
| |
| PortStatus status = getPortStatus(port_index); |
| |
| assert(status == kPortStatusFlushing |
| || status == kPortStatusFlushingToDisabled |
| || status == kPortStatusFlushingToShutdown); |
| |
| switch (status) { |
| case kPortStatusFlushing: |
| { |
| // This happens when we're flushing before a seek. |
| 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; |
| } |
| |
| case kPortStatusFlushingToDisabled: |
| { |
| // Port settings have changed and the (buggy) OMX component |
| // does not properly return buffers on disabling, we need to |
| // do a flush first and _then_ disable the port in question. |
| |
| setPortStatus(port_index, kPortStatusDisabled); |
| status_t err = mOMX->send_command( |
| mNode, OMX_CommandPortDisable, port_index); |
| assert(err == OK); |
| |
| freePortBuffers(port_index); |
| break; |
| } |
| |
| default: |
| { |
| assert(status == kPortStatusFlushingToShutdown); |
| |
| setPortStatus(port_index, kPortStatusShutdown); |
| if (getPortStatus(kPortIndexInput) == kPortStatusShutdown |
| && getPortStatus(kPortIndexOutput) == kPortStatusShutdown) { |
| status_t err = mOMX->send_command( |
| mNode, OMX_CommandStateSet, OMX_StateIdle); |
| assert(err == OK); |
| } |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| void OMXDecoder::onEventPortSettingsChanged(OMX_U32 port_index) { |
| assert(getPortStatus(port_index) == kPortStatusActive); |
| |
| status_t err; |
| |
| if (mQuirks & kDoesntReturnBuffersOnDisable) { |
| // Decoder does not properly return our buffers when disabled... |
| // Need to flush port instead and _then_ disable. |
| |
| setPortStatus(port_index, kPortStatusFlushingToDisabled); |
| |
| err = mOMX->send_command(mNode, OMX_CommandFlush, port_index); |
| } else { |
| setPortStatus(port_index, kPortStatusDisabled); |
| |
| 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); |
| |
| freePortBuffers(kPortIndexInput); |
| freePortBuffers(kPortIndexOutput); |
| } |
| } |
| |
| void OMXDecoder::initiateShutdown() { |
| Mutex::Autolock autoLock(mLock); |
| |
| if (mShutdownInitiated) { |
| return; |
| } |
| |
| if (mState == OMX_StateLoaded) { |
| return; |
| } |
| |
| assert(mState == OMX_StateExecuting); |
| |
| mShutdownInitiated = true; |
| |
| status_t err; |
| if (mQuirks & kDoesntFlushOnExecutingToIdle) { |
| if (mQuirks & kDoesntProperlyFlushAllPortsAtOnce) { |
| err = mOMX->send_command(mNode, OMX_CommandFlush, kPortIndexInput); |
| assert(err == OK); |
| |
| err = mOMX->send_command(mNode, OMX_CommandFlush, kPortIndexOutput); |
| } else { |
| err = mOMX->send_command(mNode, OMX_CommandFlush, OMX_ALL); |
| } |
| |
| setPortStatus(kPortIndexInput, kPortStatusFlushingToShutdown); |
| setPortStatus(kPortIndexOutput, kPortStatusFlushingToShutdown); |
| } else { |
| err = mClient->send_command( |
| mNode, OMX_CommandStateSet, OMX_StateIdle); |
| |
| setPortStatus(kPortIndexInput, kPortStatusShutdown); |
| setPortStatus(kPortIndexOutput, kPortStatusShutdown); |
| } |
| assert(err == OK); |
| } |
| |
| void OMXDecoder::setPortStatus(OMX_U32 port_index, PortStatus status) { |
| int shift = 3 * port_index; |
| |
| mPortStatusMask &= ~(7 << shift); |
| mPortStatusMask |= status << shift; |
| } |
| |
| OMXDecoder::PortStatus OMXDecoder::getPortStatus( |
| OMX_U32 port_index) const { |
| int shift = 3 * port_index; |
| |
| return static_cast<PortStatus>((mPortStatusMask >> shift) & 7); |
| } |
| |
| 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: |
| case kPortStatusFlushingToDisabled: |
| case kPortStatusFlushingToShutdown: |
| 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] on%sFillBufferDone (%p, size:%ld)", mComponentName, |
| msg.type == omx_message::INITIAL_FILL_BUFFER ? "Initial" : "", |
| buffer, msg.u.extended_buffer_data.range_length); |
| |
| 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: |
| case kPortStatusFlushingToDisabled: |
| case kPortStatusFlushingToShutdown: |
| 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 (mIsAVC && !(mQuirks & kWantsRawNALFrames)) { |
| 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) { |
| if (mQuirks & kMeasuresTimeInMilliseconds) { |
| timestamp = ((OMX_S64)units * 1000) / scale; |
| } else { |
| timestamp = ((OMX_S64)units * 1000000) / scale; |
| } |
| } |
| |
| input_buffer->release(); |
| input_buffer = NULL; |
| |
| LOGV("[%s] Calling EmptyBuffer on buffer %p size:%d flags:0x%08lx", |
| mComponentName, buffer, src_length, flags); |
| |
| 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(); |
| |
| if (mQuirks & kMeasuresTimeInMilliseconds) { |
| media_buffer->meta_data()->setInt32( |
| kKeyTimeUnits, |
| msg.u.extended_buffer_data.timestamp); |
| } else { |
| media_buffer->meta_data()->setInt32( |
| kKeyTimeUnits, |
| (msg.u.extended_buffer_data.timestamp + 500) / 1000); |
| } |
| |
| 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); |
| |
| media_buffer->meta_data()->setPointer( |
| kKeyBufferID, |
| msg.u.extended_buffer_data.buffer); |
| |
| 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 |
| || outputStatus == kPortStatusFlushingToDisabled |
| || outputStatus == kPortStatusFlushingToShutdown) { |
| 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, ¶ms, 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); |
| } |
| |
| void OMXDecoder::freePortBuffers(OMX_U32 port_index) { |
| BufferList *buffers = &mBuffers.editItemAt(port_index); |
| while (!buffers->empty()) { |
| IOMX::buffer_id buffer = *buffers->begin(); |
| buffers->erase(buffers->begin()); |
| |
| if (port_index == kPortIndexInput) { |
| freeInputBuffer(buffer); |
| } else { |
| freeOutputBuffer(buffer); |
| } |
| } |
| } |
| |
| } // namespace android |