1 // Copyright 2019 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include "host-common/MediaVpxDecoderGeneric.h"
15 #include "aemu/base/system/System.h"
16 #include "host-common/MediaFfmpegVideoHelper.h"
17 #include "host-common/MediaVpxVideoHelper.h"
18 #include "host-common/VpxFrameParser.h"
19 #include "android/main-emugl.h"
20 #include "android/utils/debug.h"
21 
22 #ifndef __APPLE__
23 // for Linux and Window, Cuvid is available
24 #include "host-common/MediaCudaDriverHelper.h"
25 #include "host-common/MediaCudaVideoHelper.h"
26 #endif
27 
28 #include <stdio.h>
29 #include <cassert>
30 #include <functional>
31 
32 #define MEDIA_VPX_DEBUG 0
33 
34 #if MEDIA_VPX_DEBUG
35 #define VPX_DPRINT(fmt, ...)                                                \
36     fprintf(stderr, "media-vpx-decoder-generic: %s:%d " fmt "\n", __func__, \
37             __LINE__, ##__VA_ARGS__);
38 #else
39 #define VPX_DPRINT(fmt, ...)
40 #endif
41 
42 #include <string.h>
43 
44 namespace android {
45 namespace emulation {
46 
47 using TextureFrame = MediaHostRenderer::TextureFrame;
48 
49 namespace {
50 
51 static bool s_cuvid_good = true;
52 
cudaVpxAllowed()53 static bool cudaVpxAllowed() {
54     static std::once_flag once_flag;
55     static bool s_cuda_vpx_allowed = false;
56 
57     std::call_once(once_flag, []() {
58         {
59             s_cuda_vpx_allowed =
60                     android::base::System::getEnvironmentVariable(
61                             "ANDROID_EMU_MEDIA_DECODER_CUDA_VPX") == "1";
62         }
63     });
64 
65     return s_cuda_vpx_allowed && s_cuvid_good;
66 }
67 
canUseCudaDecoder()68 bool canUseCudaDecoder() {
69     // TODO: implement a whitelist for
70     // nvidia gpu;
71 #ifndef __APPLE__
72     if (cudaVpxAllowed() && MediaCudaDriverHelper::initCudaDrivers()) {
73         VPX_DPRINT("Using Cuvid decoder on Linux/Windows");
74         return true;
75     } else {
76         VPX_DPRINT(
77                 "ERROR: cannot use cuvid decoder: failed to init cuda driver");
78         return false;
79     }
80 #else
81     return false;
82 #endif
83 }
84 
canDecodeToGpuTexture()85 bool canDecodeToGpuTexture() {
86 #ifndef __APPLE__
87 
88     if (cudaVpxAllowed() &&
89         emuglConfig_get_current_renderer() == SELECTED_RENDERER_HOST) {
90         return true;
91     } else {
92         return false;
93     }
94 #else
95     return false;
96 #endif
97 }
98 };  // end namespace
99 
MediaVpxDecoderGeneric(VpxPingInfoParser parser,MediaCodecType type)100 MediaVpxDecoderGeneric::MediaVpxDecoderGeneric(VpxPingInfoParser parser,
101                                                MediaCodecType type)
102     : mType(type),
103       mParser(parser),
104       mSnapshotHelper(mType == MediaCodecType::VP8Codec
105                               ? MediaSnapshotHelper::CodecType::VP8
106                               : MediaSnapshotHelper::CodecType::VP9) {
107     mUseGpuTexture = canDecodeToGpuTexture();
108 }
109 
~MediaVpxDecoderGeneric()110 MediaVpxDecoderGeneric::~MediaVpxDecoderGeneric() {
111     destroyVpxContext(nullptr);
112 }
113 
initVpxContext(void * ptr)114 void MediaVpxDecoderGeneric::initVpxContext(void* ptr) {
115     VPX_DPRINT("calling init context");
116 
117 #ifndef __APPLE__
118     if (canUseCudaDecoder() && mParser.version() >= 200) {
119         MediaCudaVideoHelper::OutputTreatmentMode oMode =
120                 MediaCudaVideoHelper::OutputTreatmentMode::SAVE_RESULT;
121 
122         MediaCudaVideoHelper::FrameStorageMode fMode =
123                 mUseGpuTexture ? MediaCudaVideoHelper::FrameStorageMode::
124                                          USE_GPU_TEXTURE
125                                : MediaCudaVideoHelper::FrameStorageMode::
126                                          USE_BYTE_BUFFER;
127 
128         auto cudavid = new MediaCudaVideoHelper(
129                 oMode, fMode,
130                 mType == MediaCodecType::VP8Codec ? cudaVideoCodec_VP8
131                                                   : cudaVideoCodec_VP9);
132 
133         if (mUseGpuTexture) {
134             cudavid->resetTexturePool(mRenderer.getTexturePool());
135         }
136         mHwVideoHelper.reset(cudavid);
137         if (!mHwVideoHelper->init()) {
138             mHwVideoHelper.reset(nullptr);
139         }
140     }
141 #endif
142 
143     if (mHwVideoHelper == nullptr) {
144         createAndInitSoftVideoHelper();
145     }
146 
147     VPX_DPRINT("vpx decoder initialize context successfully.");
148 }
149 
createAndInitSoftVideoHelper()150 void MediaVpxDecoderGeneric::createAndInitSoftVideoHelper() {
151     if (false && mParser.version() == 200 &&
152         (mType == MediaCodecType::VP8Codec)) {
153         // disable ffmpeg vp8 for now, until further testing
154         // vp8 and render to host
155         mSwVideoHelper.reset(new MediaFfmpegVideoHelper(
156                 mType == MediaCodecType::VP8Codec ? 8 : 9,
157                 mParser.version() < 200 ? 1 : 4));
158     } else {
159         mSwVideoHelper.reset(new MediaVpxVideoHelper(
160                 mType == MediaCodecType::VP8Codec ? 8 : 9,
161                 mParser.version() < 200 ? 1 : 4));
162     }
163     mSwVideoHelper->init();
164 }
165 
decodeFrame(void * ptr)166 void MediaVpxDecoderGeneric::decodeFrame(void* ptr) {
167     VPX_DPRINT("calling decodeFrame");
168     DecodeFrameParam param{};
169     mParser.parseDecodeFrameParams(ptr, param);
170 
171     const uint8_t* data = param.p_data;
172     unsigned int len = param.size;
173 
174     mSnapshotHelper.savePacket(data, len, param.user_priv);
175     VPX_DPRINT("calling vpx_codec_decode data %p datalen %d userdata %" PRIx64,
176                data, (int)len, param.user_priv);
177 
178     decode_internal(data, len, param.user_priv);
179 
180     // now the we can call getImage
181     fetchAllFrames();
182     ++mNumFramesDecoded;
183     VPX_DPRINT("decoded %d frames", mNumFramesDecoded);
184 }
185 
decode_internal(const uint8_t * data,size_t len,uint64_t pts)186 void MediaVpxDecoderGeneric::decode_internal(const uint8_t* data,
187                                              size_t len,
188                                              uint64_t pts) {
189     if (mTrialPeriod) {
190         try_decode(data, len, pts);
191     } else {
192         mVideoHelper->decode(data, len, pts);
193     }
194 }
195 
try_decode(const uint8_t * data,size_t len,uint64_t pts)196 void MediaVpxDecoderGeneric::try_decode(const uint8_t* data,
197                                         size_t len,
198                                         uint64_t pts) {
199     // for vpx, the first frame is enough to decide
200     // whether hw decoder can handle it
201     // probably need a whitelist for nvidia gpu
202     if (mHwVideoHelper != nullptr) {
203         mHwVideoHelper->decode(data, len, pts);
204         if (mHwVideoHelper->good()) {
205             mVideoHelper = std::move(mHwVideoHelper);
206             mTrialPeriod = false;
207             return;
208         } else {
209             VPX_DPRINT("Switching from HW to SW codec");
210             dprint("Failed to decode with HW decoder (Error Code: %d); switch "
211                    "to SW",
212                    mHwVideoHelper->error());
213             mUseGpuTexture = false;
214             if (mHwVideoHelper->fatal()) {
215                 s_cuvid_good = false;
216             }
217             mHwVideoHelper.reset(nullptr);
218         }
219     }
220     // just use libvpx, it is better quality in general
221     // except not so parallel on vp8
222     mSwVideoHelper.reset(
223             new MediaVpxVideoHelper(mType == MediaCodecType::VP8Codec ? 8 : 9,
224                                     mParser.version() < 200 ? 1 : 4));
225 
226     mSwVideoHelper->init();
227     mVideoHelper = std::move(mSwVideoHelper);
228     mVideoHelper->decode(data, len, pts);
229     mTrialPeriod = false;
230 }
231 
fetchAllFrames()232 void MediaVpxDecoderGeneric::fetchAllFrames() {
233     while (true) {
234         MediaSnapshotState::FrameInfo frame;
235         bool success = mVideoHelper->receiveFrame(&frame);
236         if (!success) {
237             break;
238         }
239         mSnapshotHelper.saveDecodedFrame(std::move(frame));
240     }
241 }
242 
getImage(void * ptr)243 void MediaVpxDecoderGeneric::getImage(void* ptr) {
244     VPX_DPRINT("calling getImage");
245     GetImageParam param{};
246     mParser.parseGetImageParams(ptr, param);
247 
248     int* retptr = param.p_error;
249     MediaSnapshotState::FrameInfo* pFrame = mSnapshotHelper.frontFrame();
250     if (pFrame == nullptr) {
251         VPX_DPRINT("there is no image");
252         *retptr = 1;
253         return;
254     }
255 
256     *retptr = 0;
257     *(param.p_fmt) = VPX_IMG_FMT_I420;
258     *(param.p_d_w) = pFrame->width;
259     *(param.p_d_h) = pFrame->height;
260     *(param.p_user_priv) = pFrame->pts;
261     VPX_DPRINT("got time %" PRIx64, pFrame->pts);
262     VPX_DPRINT(
263             "fmt is %d  I42016 is %d I420 is %d userdata is %p colorbuffer id "
264             "%d bpp %d",
265             (int)(*param.p_fmt), (int)VPX_IMG_FMT_I42016, (int)VPX_IMG_FMT_I420,
266             (void*)(*(param.p_user_priv)), param.hostColorBufferId,
267             (int)param.bpp);
268 
269     if (mParser.version() == 200) {
270         VPX_DPRINT("calling rendering to host side color buffer with id %d",
271                    param.hostColorBufferId);
272         if (mUseGpuTexture && pFrame->texture[0] > 0 && pFrame->texture[1] > 0) {
273             VPX_DPRINT(
274                     "calling rendering to host side color buffer with id %d "
275                     "(gpu texture mode: textures %u %u)",
276                     param.hostColorBufferId,
277                     pFrame->texture[0],
278                     pFrame->texture[1]);
279             mRenderer.renderToHostColorBufferWithTextures(
280                     param.hostColorBufferId, pFrame->width, pFrame->height,
281                     TextureFrame{pFrame->texture[0], pFrame->texture[1]});
282         } else {
283             VPX_DPRINT(
284                     "calling rendering to host side color buffer with id %d",
285                     param.hostColorBufferId);
286             mRenderer.renderToHostColorBuffer(param.hostColorBufferId,
287                                               pFrame->width, pFrame->height,
288                                               pFrame->data.data());
289         }
290     } else {
291         memcpy(param.p_dst, pFrame->data.data(),
292                pFrame->width * pFrame->height * 3 / 2);
293     }
294     mSnapshotHelper.discardFrontFrame();
295     VPX_DPRINT("completed getImage with colorid %d",
296                (int)param.hostColorBufferId);
297 }
298 
flush(void * ptr)299 void MediaVpxDecoderGeneric::flush(void* ptr) {
300     VPX_DPRINT("calling flush");
301     if (mVideoHelper) {
302         mVideoHelper->flush();
303         fetchAllFrames();
304     }
305     VPX_DPRINT("flush done");
306 }
307 
destroyVpxContext(void * ptr)308 void MediaVpxDecoderGeneric::destroyVpxContext(void* ptr) {
309     VPX_DPRINT("calling destroy context");
310     if (mVideoHelper != nullptr) {
311         mVideoHelper->deInit();
312         mVideoHelper.reset(nullptr);
313     }
314 }
315 
save(base::Stream * stream) const316 void MediaVpxDecoderGeneric::save(base::Stream* stream) const {
317     stream->putBe32(mParser.version());
318     stream->putBe32(mVideoHelper != nullptr ? 1 : 0);
319 
320     mSnapshotHelper.save(stream);
321 }
322 
oneShotDecode(const uint8_t * data,size_t len,uint64_t pts)323 void MediaVpxDecoderGeneric::oneShotDecode(const uint8_t* data,
324                                            size_t len,
325                                            uint64_t pts) {
326     if (!mHwVideoHelper && !mSwVideoHelper && !mVideoHelper) {
327         return;
328     }
329 
330     decode_internal(data, len, pts);
331     mVideoHelper->decode(data, len, pts);
332     while (true) {
333         MediaSnapshotState::FrameInfo frame;
334         bool success = mVideoHelper->receiveFrame(&frame);
335         if (!success) {
336             break;
337         }
338         }
339 }
340 
load(base::Stream * stream)341 bool MediaVpxDecoderGeneric::load(base::Stream* stream) {
342     VPX_DPRINT("loading libvpx now type %d",
343                mType == MediaCodecType::VP8Codec ? 8 : 9);
344     uint32_t version = stream->getBe32();
345     mParser = VpxPingInfoParser{version};
346 
347     int savedState = stream->getBe32();
348     if (savedState == 1) {
349         initVpxContext(nullptr);
350     }
351 
352     std::function<void(const uint8_t*, size_t, uint64_t)> func =
353             [=](const uint8_t* data, size_t len, uint64_t pts) {
354                 this->oneShotDecode(data, len, pts);
355             };
356 
357     if (mVideoHelper) {
358         mVideoHelper->setIgnoreDecodedFrames();
359     }
360 
361     mSnapshotHelper.load(stream, func);
362 
363     if (mVideoHelper) {
364         mVideoHelper->setSaveDecodedFrames();
365     }
366 
367     VPX_DPRINT("Done loading snapshots frames\n\n");
368     return true;
369 }
370 
371 }  // namespace emulation
372 }  // namespace android
373