/* * 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 #include "VPXDecoder.h" #include #include #include #include #include #include #include #include "vpx/vpx_decoder.h" #include "vpx/vpx_codec.h" #include "vpx/vp8dx.h" namespace android { VPXDecoder::VPXDecoder(const sp &source) : mSource(source), mStarted(false), mBufferSize(0), mCtx(NULL), mBufferGroup(NULL), mTargetTimeUs(-1) { sp 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 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) { LOGI("on2 decoder did not return a frame."); *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