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        if (outHeader->nAllocLen >= outHeader->nFilledLen) {
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        } else {
169            ALOGE("b/27597103, buffer too small");
170            android_errorWriteLog(0x534e4554, "27597103");
171            outHeader->nFilledLen = 0;
172        }
173
174        mImg = NULL;
175        outInfo->mOwnedByUs = false;
176        outQueue.erase(outQueue.begin());
177        outInfo = NULL;
178        notifyFillBufferDone(outHeader);
179        outHeader = NULL;
180    }
181
182    if (!eos) {
183        return true;
184    }
185
186    if (!outQueue.empty()) {
187        outInfo = *outQueue.begin();
188        outQueue.erase(outQueue.begin());
189        outHeader = outInfo->mHeader;
190        outHeader->nTimeStamp = 0;
191        outHeader->nFilledLen = 0;
192        outHeader->nFlags = OMX_BUFFERFLAG_EOS;
193        outInfo->mOwnedByUs = false;
194        notifyFillBufferDone(outHeader);
195        mEOSStatus = OUTPUT_FRAMES_FLUSHED;
196    }
197    return true;
198}
199
200void SoftVPX::onQueueFilled(OMX_U32 /* portIndex */) {
201    if (mOutputPortSettingsChange != NONE || mEOSStatus == OUTPUT_FRAMES_FLUSHED) {
202        return;
203    }
204
205    List<BufferInfo *> &inQueue = getPortQueue(0);
206    List<BufferInfo *> &outQueue = getPortQueue(1);
207    bool EOSseen = false;
208    bool portWillReset = false;
209
210    while ((mEOSStatus == INPUT_EOS_SEEN || !inQueue.empty())
211            && !outQueue.empty()) {
212        // Output the pending frames that left from last port reset or decoder flush.
213        if (mEOSStatus == INPUT_EOS_SEEN || mImg != NULL) {
214            if (!outputBuffers(
215                     mEOSStatus == INPUT_EOS_SEEN, true /* display */,
216                     mEOSStatus == INPUT_EOS_SEEN, &portWillReset)) {
217                ALOGE("on2 decoder failed to output frame.");
218                notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
219                return;
220            }
221            if (portWillReset || mEOSStatus == OUTPUT_FRAMES_FLUSHED ||
222                    mEOSStatus == INPUT_EOS_SEEN) {
223                return;
224            }
225            // Continue as outQueue may be empty now.
226            continue;
227        }
228
229        BufferInfo *inInfo = *inQueue.begin();
230        OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
231
232        // Software VP9 Decoder does not need the Codec Specific Data (CSD)
233        // (specified in http://www.webmproject.org/vp9/profiles/). Ignore it if
234        // it was passed.
235        if (inHeader->nFlags & OMX_BUFFERFLAG_CODECCONFIG) {
236            // Only ignore CSD buffer for VP9.
237            if (mMode == MODE_VP9) {
238                inQueue.erase(inQueue.begin());
239                inInfo->mOwnedByUs = false;
240                notifyEmptyBufferDone(inHeader);
241                continue;
242            } else {
243                // Tolerate the CSD buffer for VP8. This is a workaround
244                // for b/28689536.
245                ALOGW("WARNING: Got CSD buffer for VP8.");
246            }
247        }
248
249        mTimeStamps[mTimeStampIdx] = inHeader->nTimeStamp;
250
251        if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
252            mEOSStatus = INPUT_EOS_SEEN;
253            EOSseen = true;
254        }
255
256        if (inHeader->nFilledLen > 0) {
257            vpx_codec_err_t err = vpx_codec_decode(
258                    (vpx_codec_ctx_t *)mCtx, inHeader->pBuffer + inHeader->nOffset,
259                    inHeader->nFilledLen, &mTimeStamps[mTimeStampIdx], 0);
260            if (err == VPX_CODEC_OK) {
261                inInfo->mOwnedByUs = false;
262                inQueue.erase(inQueue.begin());
263                inInfo = NULL;
264                notifyEmptyBufferDone(inHeader);
265                inHeader = NULL;
266            } else {
267                ALOGE("on2 decoder failed to decode frame. err: %d", err);
268                notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
269                return;
270            }
271        }
272
273        mTimeStampIdx = (mTimeStampIdx + 1) % kNumBuffers;
274
275        if (!outputBuffers(
276                 EOSseen /* flushDecoder */, true /* display */, EOSseen, &portWillReset)) {
277            ALOGE("on2 decoder failed to output frame.");
278            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
279            return;
280        }
281        if (portWillReset) {
282            return;
283        }
284    }
285}
286
287void SoftVPX::onPortFlushCompleted(OMX_U32 portIndex) {
288    if (portIndex == kInputPortIndex) {
289        bool portWillReset = false;
290        if (!outputBuffers(
291                 true /* flushDecoder */, false /* display */, false /* eos */, &portWillReset)) {
292            ALOGE("Failed to flush decoder.");
293            notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
294            return;
295        }
296        mEOSStatus = INPUT_DATA_AVAILABLE;
297    }
298}
299
300void SoftVPX::onReset() {
301    bool portWillReset = false;
302    if (!outputBuffers(
303             true /* flushDecoder */, false /* display */, false /* eos */, &portWillReset)) {
304        ALOGW("Failed to flush decoder. Try to hard reset decoder");
305        destroyDecoder();
306        initDecoder();
307    }
308    mEOSStatus = INPUT_DATA_AVAILABLE;
309}
310
311}  // namespace android
312
313android::SoftOMXComponent *createSoftOMXComponent(
314        const char *name, const OMX_CALLBACKTYPE *callbacks,
315        OMX_PTR appData, OMX_COMPONENTTYPE **component) {
316    if (!strcmp(name, "OMX.google.vp8.decoder")) {
317        return new android::SoftVPX(
318                name, "video_decoder.vp8", OMX_VIDEO_CodingVP8,
319                callbacks, appData, component);
320    } else if (!strcmp(name, "OMX.google.vp9.decoder")) {
321        return new android::SoftVPX(
322                name, "video_decoder.vp9", OMX_VIDEO_CodingVP9,
323                callbacks, appData, component);
324    } else {
325        CHECK(!"Unknown component");
326    }
327    return NULL;
328}
329