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