| /* |
| * Copyright 2016, 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 "SimpleDecodingSource" |
| #include <utils/Log.h> |
| |
| #include <gui/Surface.h> |
| |
| #include <mediadrm/ICrypto.h> |
| #include <media/MediaCodecBuffer.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/foundation/ALooper.h> |
| #include <media/stagefright/foundation/AMessage.h> |
| #include <media/stagefright/foundation/AUtils.h> |
| #include <media/stagefright/MediaBuffer.h> |
| #include <media/stagefright/MediaCodecList.h> |
| #include <media/stagefright/MediaCodec.h> |
| #include <media/stagefright/MetaData.h> |
| #include <media/stagefright/SimpleDecodingSource.h> |
| #include <media/stagefright/Utils.h> |
| |
| using namespace android; |
| |
| const int64_t kTimeoutWaitForOutputUs = 500000; // 0.5 seconds |
| const int64_t kTimeoutWaitForInputUs = 0; // don't wait |
| const int kTimeoutMaxRetries = 20; |
| |
| //static |
| sp<SimpleDecodingSource> SimpleDecodingSource::Create( |
| const sp<MediaSource> &source, uint32_t flags) { |
| return SimpleDecodingSource::Create(source, flags, nullptr, nullptr); |
| } |
| |
| //static |
| sp<SimpleDecodingSource> SimpleDecodingSource::Create( |
| const sp<MediaSource> &source, uint32_t flags, const sp<ANativeWindow> &nativeWindow, |
| const char *desiredCodec, bool skipMediaCodecList) { |
| sp<Surface> surface = static_cast<Surface*>(nativeWindow.get()); |
| const char *mime = NULL; |
| sp<MetaData> meta = source->getFormat(); |
| CHECK(meta->findCString(kKeyMIMEType, &mime)); |
| |
| sp<AMessage> format = new AMessage; |
| if (convertMetaDataToMessage(source->getFormat(), &format) != OK) { |
| return NULL; |
| } |
| |
| Vector<AString> matchingCodecs; |
| MediaCodecList::findMatchingCodecs( |
| mime, false /* encoder */, flags, &matchingCodecs); |
| |
| sp<ALooper> looper = new ALooper; |
| looper->setName("stagefright"); |
| looper->start(); |
| |
| sp<MediaCodec> codec; |
| auto configure = [=](const sp<MediaCodec> &codec, const AString &componentName) |
| -> sp<SimpleDecodingSource> { |
| if (codec != NULL) { |
| ALOGI("Successfully allocated codec '%s'", componentName.c_str()); |
| |
| status_t err = codec->configure(format, surface, NULL /* crypto */, 0 /* flags */); |
| sp<AMessage> outFormat; |
| if (err == OK) { |
| err = codec->getOutputFormat(&outFormat); |
| } |
| if (err == OK) { |
| return new SimpleDecodingSource(codec, source, looper, |
| surface != NULL, |
| strcmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS) == 0, |
| outFormat); |
| } |
| |
| ALOGD("Failed to configure codec '%s'", componentName.c_str()); |
| codec->release(); |
| } |
| return NULL; |
| }; |
| |
| if (skipMediaCodecList) { |
| codec = MediaCodec::CreateByComponentName(looper, desiredCodec); |
| return configure(codec, desiredCodec); |
| } |
| |
| for (size_t i = 0; i < matchingCodecs.size(); ++i) { |
| const AString &componentName = matchingCodecs[i]; |
| if (desiredCodec != NULL && componentName.compare(desiredCodec)) { |
| continue; |
| } |
| |
| ALOGV("Attempting to allocate codec '%s'", componentName.c_str()); |
| |
| codec = MediaCodec::CreateByComponentName(looper, componentName); |
| sp<SimpleDecodingSource> res = configure(codec, componentName); |
| if (res != NULL) { |
| return res; |
| } else { |
| codec = NULL; |
| } |
| } |
| |
| looper->stop(); |
| ALOGE("No matching decoder! (mime: %s)", mime); |
| return NULL; |
| } |
| |
| SimpleDecodingSource::SimpleDecodingSource( |
| const sp<MediaCodec> &codec, const sp<MediaSource> &source, const sp<ALooper> &looper, |
| bool usingSurface, bool isVorbis, const sp<AMessage> &format) |
| : mCodec(codec), |
| mSource(source), |
| mLooper(looper), |
| mUsingSurface(usingSurface), |
| mIsVorbis(isVorbis), |
| mProtectedState(format) { |
| mCodec->getName(&mComponentName); |
| } |
| |
| SimpleDecodingSource::~SimpleDecodingSource() { |
| mCodec->release(); |
| mLooper->stop(); |
| } |
| |
| status_t SimpleDecodingSource::start(MetaData *params) { |
| (void)params; |
| Mutexed<ProtectedState>::Locked me(mProtectedState); |
| if (me->mState != INIT) { |
| return -EINVAL; |
| } |
| status_t res = mCodec->start(); |
| if (res == OK) { |
| res = mSource->start(); |
| } |
| |
| if (res == OK) { |
| me->mState = STARTED; |
| me->mQueuedInputEOS = false; |
| me->mGotOutputEOS = false; |
| } else { |
| me->mState = ERROR; |
| } |
| |
| return res; |
| } |
| |
| status_t SimpleDecodingSource::stop() { |
| Mutexed<ProtectedState>::Locked me(mProtectedState); |
| if (me->mState != STARTED) { |
| return -EINVAL; |
| } |
| |
| // wait for any pending reads to complete |
| me->mState = STOPPING; |
| while (me->mReading) { |
| me.waitForCondition(me->mReadCondition); |
| } |
| |
| status_t res1 = mCodec->stop(); |
| if (res1 != OK) { |
| mCodec->release(); |
| } |
| status_t res2 = mSource->stop(); |
| if (res1 == OK && res2 == OK) { |
| me->mState = STOPPED; |
| } else { |
| me->mState = ERROR; |
| } |
| return res1 != OK ? res1 : res2; |
| } |
| |
| sp<MetaData> SimpleDecodingSource::getFormat() { |
| Mutexed<ProtectedState>::Locked me(mProtectedState); |
| if (me->mState == STARTED || me->mState == INIT) { |
| sp<MetaData> meta = new MetaData(); |
| convertMessageToMetaData(me->mFormat, meta); |
| return meta; |
| } |
| return NULL; |
| } |
| |
| SimpleDecodingSource::ProtectedState::ProtectedState(const sp<AMessage> &format) |
| : mReading(false), |
| mFormat(format), |
| mState(INIT), |
| mQueuedInputEOS(false), |
| mGotOutputEOS(false) { |
| } |
| |
| status_t SimpleDecodingSource::read( |
| MediaBufferBase **buffer, const ReadOptions *options) { |
| *buffer = NULL; |
| |
| Mutexed<ProtectedState>::Locked me(mProtectedState); |
| if (me->mState != STARTED) { |
| return ERROR_END_OF_STREAM; |
| } |
| me->mReading = true; |
| |
| status_t res = doRead(me, buffer, options); |
| |
| me.lock(); |
| me->mReading = false; |
| if (me->mState != STARTED) { |
| me->mReadCondition.signal(); |
| } |
| |
| return res; |
| } |
| |
| status_t SimpleDecodingSource::doRead( |
| Mutexed<ProtectedState>::Locked &me, MediaBufferBase **buffer, const ReadOptions *options) { |
| // |me| is always locked on entry, but is allowed to be unlocked on exit |
| CHECK_EQ(me->mState, STARTED); |
| |
| size_t out_ix, in_ix, out_offset, out_size; |
| int64_t out_pts; |
| uint32_t out_flags; |
| status_t res; |
| |
| // flush codec on seek |
| MediaSource::ReadOptions::SeekMode mode; |
| if (options != NULL && options->getSeekTo(&out_pts, &mode)) { |
| me->mQueuedInputEOS = false; |
| me->mGotOutputEOS = false; |
| mCodec->flush(); |
| } |
| |
| if (me->mGotOutputEOS) { |
| return ERROR_END_OF_STREAM; |
| } |
| |
| for (int retries = 0; retries < kTimeoutMaxRetries; ++retries) { |
| // If we fill all available input buffers, we should expect that |
| // the codec produces at least one output buffer. Also, the codec |
| // should produce an output buffer in at most 1 seconds. Retry a |
| // few times nonetheless. |
| while (!me->mQueuedInputEOS) { |
| // allow some time to get input buffer after flush |
| res = mCodec->dequeueInputBuffer(&in_ix, kTimeoutWaitForInputUs); |
| if (res == -EAGAIN) { |
| // no available input buffers |
| break; |
| } |
| |
| sp<MediaCodecBuffer> in_buffer; |
| if (res == OK) { |
| res = mCodec->getInputBuffer(in_ix, &in_buffer); |
| } |
| |
| if (res != OK || in_buffer == NULL) { |
| ALOGW("[%s] could not get input buffer #%zu", |
| mComponentName.c_str(), in_ix); |
| me->mState = ERROR; |
| return UNKNOWN_ERROR; |
| } |
| |
| MediaBufferBase *in_buf; |
| while (true) { |
| in_buf = NULL; |
| me.unlock(); |
| res = mSource->read(&in_buf, options); |
| me.lock(); |
| if (res != OK || me->mState != STARTED) { |
| if (in_buf != NULL) { |
| in_buf->release(); |
| in_buf = NULL; |
| } |
| |
| // queue EOS |
| me->mQueuedInputEOS = true; |
| if (mCodec->queueInputBuffer( |
| in_ix, 0 /* offset */, 0 /* size */, |
| 0 /* pts */, MediaCodec::BUFFER_FLAG_EOS) != OK) { |
| ALOGI("[%s] failed to queue input EOS", mComponentName.c_str()); |
| me->mState = ERROR; |
| return UNKNOWN_ERROR; |
| } |
| |
| // don't stop on EOS, but report error or EOS on stop |
| if (res != ERROR_END_OF_STREAM) { |
| me->mState = ERROR; |
| return res; |
| } |
| if (me->mState != STARTED) { |
| return ERROR_END_OF_STREAM; |
| } |
| break; |
| } |
| if (in_buf == NULL) { // should not happen |
| continue; |
| } else if (in_buf->range_length() != 0) { |
| break; |
| } |
| in_buf->release(); |
| } |
| |
| if (in_buf != NULL) { |
| int64_t timestampUs = 0; |
| CHECK(in_buf->meta_data().findInt64(kKeyTime, ×tampUs)); |
| if (in_buf->range_length() + (mIsVorbis ? 4 : 0) > in_buffer->capacity()) { |
| ALOGW("'%s' received %zu input bytes for buffer of size %zu", |
| mComponentName.c_str(), |
| in_buf->range_length() + (mIsVorbis ? 4 : 0), in_buffer->capacity()); |
| } |
| size_t cpLen = min(in_buf->range_length(), in_buffer->capacity()); |
| memcpy(in_buffer->base(), (uint8_t *)in_buf->data() + in_buf->range_offset(), |
| cpLen); |
| |
| if (mIsVorbis) { |
| int32_t numPageSamples; |
| if (!in_buf->meta_data().findInt32(kKeyValidSamples, &numPageSamples)) { |
| numPageSamples = -1; |
| } |
| if (cpLen + sizeof(numPageSamples) <= in_buffer->capacity()) { |
| memcpy(in_buffer->base() + cpLen, &numPageSamples, sizeof(numPageSamples)); |
| cpLen += sizeof(numPageSamples); |
| } else { |
| ALOGW("Didn't have enough space to copy kKeyValidSamples"); |
| } |
| } |
| |
| res = mCodec->queueInputBuffer( |
| in_ix, 0 /* offset */, cpLen, |
| timestampUs, 0 /* flags */); |
| if (res != OK) { |
| ALOGI("[%s] failed to queue input buffer #%zu", mComponentName.c_str(), in_ix); |
| me->mState = ERROR; |
| } |
| in_buf->release(); |
| } |
| } |
| |
| me.unlock(); |
| res = mCodec->dequeueOutputBuffer( |
| &out_ix, &out_offset, &out_size, &out_pts, |
| &out_flags, kTimeoutWaitForOutputUs /* timeoutUs */); |
| me.lock(); |
| // abort read on stop |
| if (me->mState != STARTED) { |
| if (res == OK) { |
| mCodec->releaseOutputBuffer(out_ix); |
| } |
| return ERROR_END_OF_STREAM; |
| } |
| |
| if (res == -EAGAIN) { |
| ALOGD("[%s] did not produce an output buffer. retry count: %d", |
| mComponentName.c_str(), retries); |
| continue; |
| } else if (res == INFO_FORMAT_CHANGED) { |
| if (mCodec->getOutputFormat(&me->mFormat) != OK) { |
| me->mState = ERROR; |
| res = UNKNOWN_ERROR; |
| } |
| return res; |
| } else if (res == INFO_OUTPUT_BUFFERS_CHANGED) { |
| ALOGV("output buffers changed"); |
| continue; |
| } else if (res != OK) { |
| me->mState = ERROR; |
| return res; |
| } |
| |
| sp<MediaCodecBuffer> out_buffer; |
| res = mCodec->getOutputBuffer(out_ix, &out_buffer); |
| if (res != OK) { |
| ALOGW("[%s] could not get output buffer #%zu", |
| mComponentName.c_str(), out_ix); |
| me->mState = ERROR; |
| return UNKNOWN_ERROR; |
| } |
| if (out_flags & MediaCodec::BUFFER_FLAG_EOS) { |
| me->mGotOutputEOS = true; |
| // return EOS immediately if last buffer is empty |
| if (out_size == 0) { |
| mCodec->releaseOutputBuffer(out_ix); |
| return ERROR_END_OF_STREAM; |
| } |
| } |
| |
| if (mUsingSurface && out_size > 0) { |
| *buffer = new MediaBuffer(0); |
| mCodec->renderOutputBufferAndRelease(out_ix); |
| } else { |
| *buffer = new MediaBuffer(out_size); |
| CHECK_LE(out_buffer->size(), (*buffer)->size()); |
| memcpy((*buffer)->data(), out_buffer->data(), out_buffer->size()); |
| (*buffer)->meta_data().setInt64(kKeyTime, out_pts); |
| mCodec->releaseOutputBuffer(out_ix); |
| } |
| return OK; |
| } |
| |
| return TIMED_OUT; |
| } |