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