| /* |
| * Copyright (C) 2010 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 "VPXDecoder" |
| #include <utils/Log.h> |
| |
| #include "VPXDecoder.h" |
| |
| #include <OMX_Component.h> |
| |
| #include <media/stagefright/MediaBufferGroup.h> |
| #include <media/stagefright/MediaDebug.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <media/stagefright/MetaData.h> |
| #include <media/stagefright/Utils.h> |
| |
| #include "vpx/vpx_decoder.h" |
| #include "vpx/vpx_codec.h" |
| #include "vpx/vp8dx.h" |
| |
| namespace android { |
| |
| VPXDecoder::VPXDecoder(const sp<MediaSource> &source) |
| : mSource(source), |
| mStarted(false), |
| mBufferSize(0), |
| mCtx(NULL), |
| mBufferGroup(NULL), |
| mTargetTimeUs(-1) { |
| sp<MetaData> inputFormat = source->getFormat(); |
| const char *mime; |
| CHECK(inputFormat->findCString(kKeyMIMEType, &mime)); |
| CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_VPX)); |
| |
| CHECK(inputFormat->findInt32(kKeyWidth, &mWidth)); |
| CHECK(inputFormat->findInt32(kKeyHeight, &mHeight)); |
| |
| mBufferSize = (mWidth * mHeight * 3) / 2; |
| |
| mFormat = new MetaData; |
| mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); |
| mFormat->setInt32(kKeyWidth, mWidth); |
| mFormat->setInt32(kKeyHeight, mHeight); |
| mFormat->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420Planar); |
| mFormat->setCString(kKeyDecoderComponent, "VPXDecoder"); |
| |
| int64_t durationUs; |
| if (inputFormat->findInt64(kKeyDuration, &durationUs)) { |
| mFormat->setInt64(kKeyDuration, durationUs); |
| } |
| } |
| |
| VPXDecoder::~VPXDecoder() { |
| if (mStarted) { |
| stop(); |
| } |
| } |
| |
| status_t VPXDecoder::start(MetaData *) { |
| if (mStarted) { |
| return UNKNOWN_ERROR; |
| } |
| |
| status_t err = mSource->start(); |
| |
| if (err != OK) { |
| return err; |
| } |
| |
| mCtx = new vpx_codec_ctx_t; |
| vpx_codec_err_t vpx_err; |
| if ((vpx_err = vpx_codec_dec_init( |
| (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, NULL, 0))) { |
| LOGE("on2 decoder failed to initialize. (%d)", vpx_err); |
| |
| mSource->stop(); |
| |
| return UNKNOWN_ERROR; |
| } |
| |
| mBufferGroup = new MediaBufferGroup; |
| mBufferGroup->add_buffer(new MediaBuffer(mBufferSize)); |
| mBufferGroup->add_buffer(new MediaBuffer(mBufferSize)); |
| |
| mTargetTimeUs = -1; |
| |
| mStarted = true; |
| |
| return OK; |
| } |
| |
| status_t VPXDecoder::stop() { |
| if (!mStarted) { |
| return UNKNOWN_ERROR; |
| } |
| |
| delete mBufferGroup; |
| mBufferGroup = NULL; |
| |
| vpx_codec_destroy((vpx_codec_ctx_t *)mCtx); |
| delete (vpx_codec_ctx_t *)mCtx; |
| mCtx = NULL; |
| |
| mSource->stop(); |
| |
| mStarted = false; |
| |
| return OK; |
| } |
| |
| sp<MetaData> VPXDecoder::getFormat() { |
| return mFormat; |
| } |
| |
| status_t VPXDecoder::read( |
| MediaBuffer **out, const ReadOptions *options) { |
| *out = NULL; |
| |
| bool seeking = false; |
| int64_t seekTimeUs; |
| ReadOptions::SeekMode seekMode; |
| if (options && options->getSeekTo(&seekTimeUs, &seekMode)) { |
| seeking = true; |
| } |
| |
| MediaBuffer *input; |
| status_t err = mSource->read(&input, options); |
| |
| if (err != OK) { |
| return err; |
| } |
| |
| LOGV("read %d bytes from source\n", input->range_length()); |
| |
| if (seeking) { |
| int64_t targetTimeUs; |
| if (input->meta_data()->findInt64(kKeyTargetTime, &targetTimeUs) |
| && targetTimeUs >= 0) { |
| mTargetTimeUs = targetTimeUs; |
| } else { |
| mTargetTimeUs = -1; |
| } |
| } |
| |
| if (vpx_codec_decode( |
| (vpx_codec_ctx_t *)mCtx, |
| (uint8_t *)input->data() + input->range_offset(), |
| input->range_length(), |
| NULL, |
| 0)) { |
| LOGE("on2 decoder failed to decode frame."); |
| input->release(); |
| input = NULL; |
| |
| return UNKNOWN_ERROR; |
| } |
| |
| LOGV("successfully decoded 1 or more frames."); |
| |
| int64_t timeUs; |
| CHECK(input->meta_data()->findInt64(kKeyTime, &timeUs)); |
| |
| input->release(); |
| input = NULL; |
| |
| bool skipFrame = false; |
| |
| if (mTargetTimeUs >= 0) { |
| CHECK(timeUs <= mTargetTimeUs); |
| |
| if (timeUs < mTargetTimeUs) { |
| // We're still waiting for the frame with the matching |
| // timestamp and we won't return the current one. |
| skipFrame = true; |
| |
| LOGV("skipping frame at %lld us", timeUs); |
| } else { |
| LOGV("found target frame at %lld us", timeUs); |
| |
| mTargetTimeUs = -1; |
| } |
| } |
| |
| if (skipFrame) { |
| *out = new MediaBuffer(0); |
| return OK; |
| } |
| |
| vpx_codec_iter_t iter = NULL; |
| vpx_image_t *img = vpx_codec_get_frame((vpx_codec_ctx_t *)mCtx, &iter); |
| |
| if (img == NULL) { |
| // The VPX format supports "internal-only" frames that are |
| // referenced by future content but never actually displayed, so |
| // this is a perfectly valid scenario. |
| |
| *out = new MediaBuffer(0); |
| return OK; |
| } |
| |
| CHECK_EQ(img->fmt, IMG_FMT_I420); |
| |
| int32_t width = img->d_w; |
| int32_t height = img->d_h; |
| |
| if (width != mWidth || height != mHeight) { |
| LOGI("Image dimensions changed, width = %d, height = %d", |
| width, height); |
| |
| mWidth = width; |
| mHeight = height; |
| mFormat->setInt32(kKeyWidth, width); |
| mFormat->setInt32(kKeyHeight, height); |
| |
| mBufferSize = (mWidth * mHeight * 3) / 2; |
| delete mBufferGroup; |
| mBufferGroup = new MediaBufferGroup; |
| mBufferGroup->add_buffer(new MediaBuffer(mBufferSize)); |
| mBufferGroup->add_buffer(new MediaBuffer(mBufferSize)); |
| |
| return INFO_FORMAT_CHANGED; |
| } |
| |
| MediaBuffer *output; |
| CHECK_EQ(mBufferGroup->acquire_buffer(&output), OK); |
| |
| const uint8_t *srcLine = (const uint8_t *)img->planes[PLANE_Y]; |
| uint8_t *dst = (uint8_t *)output->data(); |
| for (size_t i = 0; i < img->d_h; ++i) { |
| memcpy(dst, srcLine, img->d_w); |
| |
| srcLine += img->stride[PLANE_Y]; |
| dst += img->d_w; |
| } |
| |
| srcLine = (const uint8_t *)img->planes[PLANE_U]; |
| for (size_t i = 0; i < img->d_h / 2; ++i) { |
| memcpy(dst, srcLine, img->d_w / 2); |
| |
| srcLine += img->stride[PLANE_U]; |
| dst += img->d_w / 2; |
| } |
| |
| srcLine = (const uint8_t *)img->planes[PLANE_V]; |
| for (size_t i = 0; i < img->d_h / 2; ++i) { |
| memcpy(dst, srcLine, img->d_w / 2); |
| |
| srcLine += img->stride[PLANE_V]; |
| dst += img->d_w / 2; |
| } |
| |
| output->set_range(0, (width * height * 3) / 2); |
| |
| output->meta_data()->setInt64(kKeyTime, timeUs); |
| |
| *out = output; |
| |
| return OK; |
| } |
| |
| } // namespace android |
| |