SoftVPX.cpp revision 9486e0a16e9ad4d4f6bd5047a3cbb1b3f2008d65
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//#define LOG_NDEBUG 0
18#define LOG_TAG "SoftVPX"
19#include <utils/Log.h>
20#include <utils/misc.h>
21#include "OMX_VideoExt.h"
22
23#include "SoftVPX.h"
24
25#include <media/stagefright/foundation/ADebug.h>
26#include <media/stagefright/MediaDefs.h>
27
28
29namespace android {
30
31// Only need to declare the highest supported profile and level here.
32static const CodecProfileLevel kVP9ProfileLevels[] = {
33    { OMX_VIDEO_VP9Profile0, OMX_VIDEO_VP9Level5  },
34};
35
36SoftVPX::SoftVPX(
37        const char *name,
38        const char *componentRole,
39        OMX_VIDEO_CODINGTYPE codingType,
40        const OMX_CALLBACKTYPE *callbacks,
41        OMX_PTR appData,
42        OMX_COMPONENTTYPE **component)
43    : SoftVideoDecoderOMXComponent(
44            name, componentRole, codingType,
45            codingType == OMX_VIDEO_CodingVP8 ? NULL : kVP9ProfileLevels,
46            codingType == OMX_VIDEO_CodingVP8 ?  0 : NELEM(kVP9ProfileLevels),
47            320 /* width */, 240 /* height */, callbacks, appData, component),
48      mMode(codingType == OMX_VIDEO_CodingVP8 ? MODE_VP8 : MODE_VP9),
49      mEOSStatus(INPUT_DATA_AVAILABLE),
50      mCtx(NULL),
51      mFrameParallelMode(false),
52      mTimeStampIdx(0),
53      mImg(NULL) {
54    // arbitrary from avc/hevc as vpx does not specify a min compression ratio
55    const size_t kMinCompressionRatio = mMode == MODE_VP8 ? 2 : 4;
56    const char *mime = mMode == MODE_VP8 ? MEDIA_MIMETYPE_VIDEO_VP8 : MEDIA_MIMETYPE_VIDEO_VP9;
57    const size_t kMaxOutputBufferSize = 2048 * 2048 * 3 / 2;
58    initPorts(
59            kNumBuffers, kMaxOutputBufferSize / kMinCompressionRatio /* inputBufferSize */,
60            kNumBuffers, mime, kMinCompressionRatio);
61    CHECK_EQ(initDecoder(), (status_t)OK);
62}
63
64SoftVPX::~SoftVPX() {
65    destroyDecoder();
66}
67
68static int GetCPUCoreCount() {
69    int cpuCoreCount = 1;
70#if defined(_SC_NPROCESSORS_ONLN)
71    cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
72#else
73    // _SC_NPROC_ONLN must be defined...
74    cpuCoreCount = sysconf(_SC_NPROC_ONLN);
75#endif
76    CHECK(cpuCoreCount >= 1);
77    ALOGV("Number of CPU cores: %d", cpuCoreCount);
78    return cpuCoreCount;
79}
80
81status_t SoftVPX::initDecoder() {
82    mCtx = new vpx_codec_ctx_t;
83    vpx_codec_err_t vpx_err;
84    vpx_codec_dec_cfg_t cfg;
85    vpx_codec_flags_t flags;
86    memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t));
87    memset(&flags, 0, sizeof(vpx_codec_flags_t));
88    cfg.threads = GetCPUCoreCount();
89
90    if (mFrameParallelMode) {
91        flags |= VPX_CODEC_USE_FRAME_THREADING;
92    }
93
94    if ((vpx_err = vpx_codec_dec_init(
95                (vpx_codec_ctx_t *)mCtx,
96                 mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo,
97                 &cfg, flags))) {
98        ALOGE("on2 decoder failed to initialize. (%d)", vpx_err);
99        return UNKNOWN_ERROR;
100    }
101
102    return OK;
103}
104
105status_t SoftVPX::destroyDecoder() {
106    vpx_codec_destroy((vpx_codec_ctx_t *)mCtx);
107    delete (vpx_codec_ctx_t *)mCtx;
108    mCtx = NULL;
109    return OK;
110}
111
112bool SoftVPX::outputBuffers(bool flushDecoder, bool display, bool eos, bool *portWillReset) {
113    List<BufferInfo *> &outQueue = getPortQueue(1);
114    BufferInfo *outInfo = NULL;
115    OMX_BUFFERHEADERTYPE *outHeader = NULL;
116    vpx_codec_iter_t iter = NULL;
117
118    if (flushDecoder && mFrameParallelMode) {
119        // Flush decoder by passing NULL data ptr and 0 size.
120        // Ideally, this should never fail.
121        if (vpx_codec_decode((vpx_codec_ctx_t *)mCtx, NULL, 0, NULL, 0)) {
122            ALOGE("Failed to flush on2 decoder.");
123            return false;
124        }
125    }
126
127    if (!display) {
128        if (!flushDecoder) {
129            ALOGE("Invalid operation.");
130            return false;
131        }
132        // Drop all the decoded frames in decoder.
133        while ((mImg = vpx_codec_get_frame((vpx_codec_ctx_t *)mCtx, &iter))) {
134        }
135        return true;
136    }
137
138    while (!outQueue.empty()) {
139        if (mImg == NULL) {
140            mImg = vpx_codec_get_frame((vpx_codec_ctx_t *)mCtx, &iter);
141            if (mImg == NULL) {
142                break;
143            }
144        }
145        uint32_t width = mImg->d_w;
146        uint32_t height = mImg->d_h;
147        outInfo = *outQueue.begin();
148        outHeader = outInfo->mHeader;
149        CHECK_EQ(mImg->fmt, VPX_IMG_FMT_I420);
150        handlePortSettingsChange(portWillReset, width, height);
151        if (*portWillReset) {
152            return true;
153        }
154
155        outHeader->nOffset = 0;
156        outHeader->nFlags = 0;
157        outHeader->nFilledLen = (outputBufferWidth() * outputBufferHeight() * 3) / 2;
158        outHeader->nTimeStamp = *(OMX_TICKS *)mImg->user_priv;
159
160        uint8_t *dst = outHeader->pBuffer;
161        const uint8_t *srcY = (const uint8_t *)mImg->planes[VPX_PLANE_Y];
162        const uint8_t *srcU = (const uint8_t *)mImg->planes[VPX_PLANE_U];
163        const uint8_t *srcV = (const uint8_t *)mImg->planes[VPX_PLANE_V];
164        size_t srcYStride = mImg->stride[VPX_PLANE_Y];
165        size_t srcUStride = mImg->stride[VPX_PLANE_U];
166        size_t srcVStride = mImg->stride[VPX_PLANE_V];
167        copyYV12FrameToOutputBuffer(dst, srcY, srcU, srcV, srcYStride, srcUStride, srcVStride);
168
169        mImg = NULL;
170        outInfo->mOwnedByUs = false;
171        outQueue.erase(outQueue.begin());
172        outInfo = NULL;
173        notifyFillBufferDone(outHeader);
174        outHeader = NULL;
175    }
176
177    if (!eos) {
178        return true;
179    }
180
181    if (!outQueue.empty()) {
182        outInfo = *outQueue.begin();
183        outQueue.erase(outQueue.begin());
184        outHeader = outInfo->mHeader;
185        outHeader->nTimeStamp = 0;
186        outHeader->nFilledLen = 0;
187        outHeader->nFlags = OMX_BUFFERFLAG_EOS;
188        outInfo->mOwnedByUs = false;
189        notifyFillBufferDone(outHeader);
190        mEOSStatus = OUTPUT_FRAMES_FLUSHED;
191    }
192    return true;
193}
194
195void SoftVPX::onQueueFilled(OMX_U32 /* portIndex */) {
196    if (mOutputPortSettingsChange != NONE || mEOSStatus == OUTPUT_FRAMES_FLUSHED) {
197        return;
198    }
199
200    List<BufferInfo *> &inQueue = getPortQueue(0);
201    List<BufferInfo *> &outQueue = getPortQueue(1);
202    bool EOSseen = false;
203    bool portWillReset = false;
204
205    while ((mEOSStatus == INPUT_EOS_SEEN || !inQueue.empty())
206            && !outQueue.empty()) {
207        // Output the pending frames that left from last port reset or decoder flush.
208        if (mEOSStatus == INPUT_EOS_SEEN || mImg != NULL) {
209            if (!outputBuffers(
210                     mEOSStatus == INPUT_EOS_SEEN, true /* display */,
211                     mEOSStatus == INPUT_EOS_SEEN, &portWillReset)) {
212                ALOGE("on2 decoder failed to output frame.");
213                notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
214                return;
215            }
216            if (portWillReset || mEOSStatus == OUTPUT_FRAMES_FLUSHED ||
217                    mEOSStatus == INPUT_EOS_SEEN) {
218                return;
219            }
220            // Continue as outQueue may be empty now.
221            continue;
222        }
223
224        BufferInfo *inInfo = *inQueue.begin();
225        OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
226        mTimeStamps[mTimeStampIdx] = inHeader->nTimeStamp;
227
228        if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
229            mEOSStatus = INPUT_EOS_SEEN;
230            EOSseen = true;
231        }
232
233        if (inHeader->nFilledLen > 0) {
234            vpx_codec_err_t err = vpx_codec_decode(
235                    (vpx_codec_ctx_t *)mCtx, inHeader->pBuffer + inHeader->nOffset,
236                    inHeader->nFilledLen, &mTimeStamps[mTimeStampIdx], 0);
237            if (err == VPX_CODEC_OK) {
238                inInfo->mOwnedByUs = false;
239                inQueue.erase(inQueue.begin());
240                inInfo = NULL;
241                notifyEmptyBufferDone(inHeader);
242                inHeader = NULL;
243            } else {
244                ALOGE("on2 decoder failed to decode frame.");
245                notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
246                return;
247            }
248        }
249
250        mTimeStampIdx = (mTimeStampIdx + 1) % kNumBuffers;
251
252        if (!outputBuffers(
253                 EOSseen /* flushDecoder */, true /* display */, EOSseen, &portWillReset)) {
254            ALOGE("on2 decoder failed to output frame.");
255            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
256            return;
257        }
258        if (portWillReset) {
259            return;
260        }
261    }
262}
263
264void SoftVPX::onPortFlushCompleted(OMX_U32 portIndex) {
265    if (portIndex == kInputPortIndex) {
266        bool portWillReset = false;
267        if (!outputBuffers(
268                 true /* flushDecoder */, false /* display */, false /* eos */, &portWillReset)) {
269            ALOGE("Failed to flush decoder.");
270            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
271            return;
272        }
273        mEOSStatus = INPUT_DATA_AVAILABLE;
274    }
275}
276
277void SoftVPX::onReset() {
278    bool portWillReset = false;
279    if (!outputBuffers(
280             true /* flushDecoder */, false /* display */, false /* eos */, &portWillReset)) {
281        ALOGW("Failed to flush decoder. Try to hard reset decoder");
282        destroyDecoder();
283        initDecoder();
284    }
285    mEOSStatus = INPUT_DATA_AVAILABLE;
286}
287
288}  // namespace android
289
290android::SoftOMXComponent *createSoftOMXComponent(
291        const char *name, const OMX_CALLBACKTYPE *callbacks,
292        OMX_PTR appData, OMX_COMPONENTTYPE **component) {
293    if (!strcmp(name, "OMX.google.vp8.decoder")) {
294        return new android::SoftVPX(
295                name, "video_decoder.vp8", OMX_VIDEO_CodingVP8,
296                callbacks, appData, component);
297    } else if (!strcmp(name, "OMX.google.vp9.decoder")) {
298        return new android::SoftVPX(
299                name, "video_decoder.vp9", OMX_VIDEO_CodingVP9,
300                callbacks, appData, component);
301    } else {
302        CHECK(!"Unknown component");
303    }
304    return NULL;
305}
306