From 45664dac2aaa1920a145c4ea233f219a297b0fa7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 21 Nov 2013 08:38:49 -0500 Subject: [PATCH] Draw directly to the surface buffer. Improve amount of decoding and rendering that can be done in parallel. Add performance levels and choose them by cpuinfo. Improves Tegra 3 performance significantly. --- jni/nv_avc_dec/Android.mk | 2 +- jni/nv_avc_dec/nv_avc_dec.c | 169 +++++++++++------- jni/nv_avc_dec/nv_avc_dec.h | 7 +- jni/nv_avc_dec/nv_avc_dec_jni.c | 31 +--- libs/armeabi-v7a/libnv_avc_dec.so | Bin 17700 -> 17700 bytes libs/x86/libnv_avc_dec.so | Bin 9568 -> 9580 bytes src/com/limelight/Game.java | 7 +- .../nvstream/av/video/AvcDecoder.java | 7 +- .../nvstream/av/video/CpuDecoderRenderer.java | 115 +++++++++--- 9 files changed, 218 insertions(+), 120 deletions(-) diff --git a/jni/nv_avc_dec/Android.mk b/jni/nv_avc_dec/Android.mk index 7aaed9f4..c11bc583 100644 --- a/jni/nv_avc_dec/Android.mk +++ b/jni/nv_avc_dec/Android.mk @@ -9,7 +9,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := nv_avc_dec LOCAL_SRC_FILES := nv_avc_dec.c nv_avc_dec_jni.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/ffmpeg/$(TARGET_ARCH_ABI)/include -LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog +LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -landroid # Link to ffmpeg libraries LOCAL_SHARED_LIBRARIES := libavcodec libavformat libswscale libavutil libavfilter libwsresample diff --git a/jni/nv_avc_dec/nv_avc_dec.c b/jni/nv_avc_dec/nv_avc_dec.c index f66c5927..8758318e 100644 --- a/jni/nv_avc_dec/nv_avc_dec.c +++ b/jni/nv_avc_dec/nv_avc_dec.c @@ -5,20 +5,31 @@ #include #include "nv_avc_dec.h" +#include +#include + AVCodec* decoder; AVCodecContext* decoder_ctx; AVFrame* yuv_frame; -AVFrame* tmp_frame; AVFrame* rgb_frame; +AVFrame* rnd_frame; +AVFrame* dec_frame; pthread_mutex_t mutex; char* rgb_frame_buf; -int picture_valid; -int rgb_dirty; struct SwsContext* scaler_ctx; +int picture_new; + +#define RENDER_PIX_FMT AV_PIX_FMT_RGBA +#define BYTES_PER_PIXEL 4 + +#define VERY_LOW_PERF 0 +#define LOW_PERF 1 +#define MED_PERF 2 +#define HIGH_PERF 3 // This function must be called before // any other decoding functions -int nv_avc_init(int width, int height) { +int nv_avc_init(int width, int height, int perf_lvl) { int err; pthread_mutex_init(&mutex, NULL); @@ -44,12 +55,23 @@ int nv_avc_init(int width, int height) { // Show frames even before a reference frame decoder_ctx->flags2 |= CODEC_FLAG2_SHOW_ALL; - // Skip the loop filter for performance reasons - decoder_ctx->skip_loop_filter = AVDISCARD_ALL; + if (perf_lvl <= LOW_PERF) { + // Skip the loop filter for performance reasons + decoder_ctx->skip_loop_filter = AVDISCARD_ALL; + } - // Run 2 threads for decoding - decoder_ctx->thread_count = 2; - decoder_ctx->thread_type = FF_THREAD_FRAME; + if (perf_lvl <= MED_PERF) { + // Run 2 threads for decoding + decoder_ctx->thread_count = 2; + decoder_ctx->thread_type = FF_THREAD_FRAME; + + // Use some tricks to make things faster + decoder_ctx->flags2 |= CODEC_FLAG2_FAST; + } + else { + // Use low delay single threaded encoding + decoder_ctx->flags |= CODEC_FLAG_LOW_DELAY; + } decoder_ctx->width = width; decoder_ctx->height = height; @@ -62,13 +84,13 @@ int nv_avc_init(int width, int height) { return err; } - tmp_frame = av_frame_alloc(); - if (tmp_frame == NULL) { + dec_frame = av_frame_alloc(); + if (dec_frame == NULL) { __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", "Couldn't allocate frame"); return -1; } - + rgb_frame = av_frame_alloc(); if (rgb_frame == NULL) { __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", @@ -76,16 +98,16 @@ int nv_avc_init(int width, int height) { return -1; } - rgb_frame_buf = (char*)av_malloc(nv_avc_get_rgb_frame_size()); + rgb_frame_buf = (char*)av_malloc(width * height * BYTES_PER_PIXEL); if (rgb_frame_buf == NULL) { __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", "Couldn't allocate picture"); return -1; } - + err = avpicture_fill((AVPicture*)rgb_frame, rgb_frame_buf, - AV_PIX_FMT_RGB32, + RENDER_PIX_FMT, decoder_ctx->width, decoder_ctx->height); if (err < 0) { @@ -93,13 +115,13 @@ int nv_avc_init(int width, int height) { "Couldn't fill picture"); return err; } - + scaler_ctx = sws_getContext(decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, decoder_ctx->width, decoder_ctx->height, - AV_PIX_FMT_RGB32, + RENDER_PIX_FMT, SWS_BICUBIC, NULL, NULL, NULL); if (scaler_ctx == NULL) { @@ -123,9 +145,13 @@ void nv_avc_destroy(void) { sws_freeContext(scaler_ctx); scaler_ctx = NULL; } - if (tmp_frame) { + if (dec_frame) { + av_frame_free(&dec_frame); + dec_frame = NULL; + } + if (yuv_frame) { av_frame_free(&yuv_frame); - tmp_frame = NULL; + yuv_frame = NULL; } if (rgb_frame) { av_frame_free(&rgb_frame); @@ -135,32 +161,43 @@ void nv_avc_destroy(void) { av_free(rgb_frame_buf); rgb_frame_buf = NULL; } + if (rnd_frame) { + av_frame_free(&rnd_frame); + rnd_frame = NULL; + } pthread_mutex_destroy(&mutex); } -// The decoded frame is ARGB -// Returns 1 on success, 0 on failure -int nv_avc_get_current_frame(char* rgbframe, int size) { +void nv_avc_redraw(JNIEnv *env, jobject surface) { + ANativeWindow* window; + ANativeWindow_Buffer buffer; int err; - if (size != nv_avc_get_rgb_frame_size()) { - return 0; + // Free the old decoded frame + if (rnd_frame) { + av_frame_free(&rnd_frame); } pthread_mutex_lock(&mutex); - // Check if the RGB frame needs updating - if (rgb_dirty) { - // If the decoder doesn't have a new picture, we fail - if (!picture_valid) { - pthread_mutex_unlock(&mutex); - return 0; + // Check if there's a new frame + if (picture_new) { + // Clone the decoder's last frame + rnd_frame = av_frame_clone(yuv_frame); + + // The remaining processing can be done without the mutex + pthread_mutex_unlock(&mutex); + + if (rnd_frame == NULL) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Cloning failed"); + return; } // Convert the YUV image to RGB err = sws_scale(scaler_ctx, - yuv_frame->data, - yuv_frame->linesize, + rnd_frame->data, + rnd_frame->linesize, 0, decoder_ctx->height, rgb_frame->data, @@ -168,36 +205,42 @@ int nv_avc_get_current_frame(char* rgbframe, int size) { if (err != decoder_ctx->height) { __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", "Scaling failed"); - pthread_mutex_unlock(&mutex); - return 0; + return; } - // RGB frame is now clean - rgb_dirty = 0; + window = ANativeWindow_fromSurface(env, surface); + if (window == NULL) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Failed to get window from surface"); + return; + } + + // Lock down a render buffer + if (ANativeWindow_lock(window, &buffer, NULL) >= 0) { + // Draw the frame to the buffer + err = avpicture_layout((AVPicture*)rgb_frame, + RENDER_PIX_FMT, + decoder_ctx->width, + decoder_ctx->height, + buffer.bits, + decoder_ctx->width * + decoder_ctx->height * + BYTES_PER_PIXEL); + if (err < 0) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Picture fill failed"); + } + + // Draw the frame to the surface + ANativeWindow_unlockAndPost(window); + } + + ANativeWindow_release(window); } - - // The remaining processing can be done without the mutex - pthread_mutex_unlock(&mutex); - - err = avpicture_layout((AVPicture*)rgb_frame, - AV_PIX_FMT_RGB32, - decoder_ctx->width, - decoder_ctx->height, - rgbframe, - size); - if (err < 0) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Picture fill failed"); - return 0; + else { + pthread_mutex_unlock(&mutex); + rnd_frame = NULL; } - - return 1; -} - -int nv_avc_get_rgb_frame_size(void) { - return avpicture_get_size(AV_PIX_FMT_RGB32, - decoder_ctx->width, - decoder_ctx->height); } // packets must be decoded in order @@ -218,7 +261,7 @@ int nv_avc_decode(unsigned char* indata, int inlen) { while (pkt.size > 0) { err = avcodec_decode_video2( decoder_ctx, - tmp_frame, + dec_frame, &got_pic, &pkt); if (err < 0) { @@ -237,14 +280,12 @@ int nv_avc_decode(unsigned char* indata, int inlen) { } // Clone a new frame - yuv_frame = av_frame_clone(tmp_frame); + yuv_frame = av_frame_clone(dec_frame); if (yuv_frame) { - // If we got a new picture, the RGB frame needs refreshing - picture_valid = 1; - rgb_dirty = 1; + picture_new = 1; } else { - picture_valid = 0; + picture_new = 0; } pthread_mutex_unlock(&mutex); diff --git a/jni/nv_avc_dec/nv_avc_dec.h b/jni/nv_avc_dec/nv_avc_dec.h index ea70ce38..d53d5425 100644 --- a/jni/nv_avc_dec/nv_avc_dec.h +++ b/jni/nv_avc_dec/nv_avc_dec.h @@ -1,5 +1,6 @@ -int nv_avc_init(int width, int height); +#include + +int nv_avc_init(int width, int height, int perf_lvl); void nv_avc_destroy(void); -int nv_avc_get_current_frame(char* yuvframe, int size); -int nv_avc_get_frame_size(void); +void nv_avc_redraw(JNIEnv *env, jobject surface); int nv_avc_decode(unsigned char* indata, int inlen); diff --git a/jni/nv_avc_dec/nv_avc_dec_jni.c b/jni/nv_avc_dec/nv_avc_dec_jni.c index b8d3442b..e438c703 100644 --- a/jni/nv_avc_dec/nv_avc_dec_jni.c +++ b/jni/nv_avc_dec/nv_avc_dec_jni.c @@ -6,8 +6,10 @@ // This function must be called before // any other decoding functions JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_video_AvcDecoder_init(JNIEnv *env, jobject this, jint width, jint height) { - return nv_avc_init(width, height); +Java_com_limelight_nvstream_av_video_AvcDecoder_init(JNIEnv *env, jobject this, jint width, + jint height, jint perflvl) +{ + return nv_avc_init(width, height, perflvl); } // This function must be called after @@ -17,27 +19,10 @@ Java_com_limelight_nvstream_av_video_AvcDecoder_destroy(JNIEnv *env, jobject thi nv_avc_destroy(); } -// The decoded frame is ARGB -// Returns 1 on success, 0 on failure -JNIEXPORT jboolean JNICALL -Java_com_limelight_nvstream_av_video_AvcDecoder_getCurrentFrame(JNIEnv *env, jobject this, - jintArray rgbframe, jint sizeints) -{ - jint* jni_rgbframe; - jboolean ret; - - jni_rgbframe = (*env)->GetIntArrayElements(env, rgbframe, 0); - - ret = (nv_avc_get_current_frame((char*)jni_rgbframe, sizeints*4) != 0) ? JNI_TRUE : JNI_FALSE; - - (*env)->ReleaseIntArrayElements(env, rgbframe, jni_rgbframe, 0); - - return ret; -} - -JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_video_AvcDecoder_getFrameSize(JNIEnv *env, jobject this) { - return nv_avc_get_rgb_frame_size() / 4; +// This function redraws the surface +JNIEXPORT void JNICALL +Java_com_limelight_nvstream_av_video_AvcDecoder_redraw(JNIEnv *env, jobject this, jobject surface) { + nv_avc_redraw(env, surface); } // packets must be decoded in order diff --git a/libs/armeabi-v7a/libnv_avc_dec.so b/libs/armeabi-v7a/libnv_avc_dec.so index 75fa000199f3f2275af1f19a7b97177c6e4fd92b..2d1c289d1afab7eaef53641c405239895c88efa5 100644 GIT binary patch delta 5805 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 delta 5754 zcmb7IeOOdg8b5a!VfZiy0R=(0@{xS%0J>6XA|DrUFfBhCtK9)%bZF6GVf-p~@3>mJ zt*zM2qO7!XTS`<2-6x;bq}6<=M>l%%X$`HWh)|P>o^_k4C%gN5@7&Sr{IwmPm*4w) z&wI~3=RNN^_srS%1h?-AuC2bjwvaKBF$~4X7zgN^16kmg**eDRA)#tLRCW&a=TJ{# z!2_0^uTRe}&SxwjJd7lIIFsBrut9hQW;* z{$KFN5*X_M(pxy1L$y#hVuN@_5Wh2sFT9nDY~`&6<_zMSgZPs{{DmOCA6%~)Ko59c z;>|F582dPgUk&2Akihu;nH3$xdGP$Cn-jSd{0?x+Ws2aF;B&z5Brd{m z7KZgO#HtPv@cb#=-#{wrr-1(%^+jsEg<4WeDzFLsn#O-C_~C571HJGS@O>Ko5Aahe z7s=ss7|v-NeguCg=Vk+OSl?}^4|H&A#vf)@dy|Wd z49zgKVFf>pNKgY?Z#pRTFM>y9FjlSB9{@k5>F~SYof>`t{EUYG1Rk#$P%Jj2CWT}E z>4mv4STb+U*YQtv zZC0vOOls$d*R5@Cwyklhtu{IB&9>TlE48=5+UThJt#l}ym*yHK+#YD8v38?ltyAeq zeM7TzBMVflt8Hwkmt0}_(&u4uQj8&2T5Ko}QwJx_k60Rx5m5Zr*rewo4vfRwOZ_hw zXl&A_5&3DbDP%Eq+-j}0)vjo;u3fXfVNJcYuBpk|)SM5$LP?A)=;@10)=SsMrQgPC zF@yCG+Ta#T(OitwWz;Wy5R8^2zS&CB+ylHHG6{mOKYIW|Wwsxb|J2e#4aXNxSyBS9 zj_lIT@iAf~X=PjdTkY$khkYoszYRGcPuOJ1GG6)qUH(Ls!EtNKv8pv-T;gCRC5m12G z_}C0c1|&;*IXY+O8H3ajoi1ICHehpOYyt&34vUW6MpRSAQ%#oQ1glZ4Nzr)3X!Jxh zK2gksg;%Y4z$jHGh|}nkH97}KSRu6K;84k`ApXK(?n96%h??|HOia0sQRn=*LpuFYOm*sCh@$Hi zJ7oI=4P zLL5R^GgYF{^PRxJ^nBT}5uMPpJxZD#}B*fgb0wBig;0-=I3Y4+J0X3M2$54y6C!2X332LsfiM;a;Ju_E zA@&hYz$b|iJ?bRHTI?c3ejg@8vb;rzwQ-CPDcDQM<2XV%1;Zpv#ixr9iO^4owR?^* zgR%33NY0CdSqP|`6l^PB5hBAb5h5Y35+ZX)2{H0(gxFeqgmZDeA}mIP-FZWL_nx8K z+&kQc(YJ@HDH~7k_%h`E?O#^^aBL``mud9b0X+szGXey9F7%$V35z(4%(y=*`Leqh*QqMEi4&0S7la3(p+G#+7$juGl905wG#c`PHpuu5!zV zCVG|dI&9ZG)68F63hgYXpg+$%Xi2e8Jt|V4Jf0$;sqrD`eKcy;^Pno~5?2$6rd(J~Gp7tcU zSz2LDCG=iTtT}Fe@{~c#D;A}dy=Q%Tm(l*bwEMTNSw^FC(m^<@@vu;muY-VL6P zk}yF(Fr#Xw(oO+dcX|pD{`eaHXn|N&pqM`xmc4e5$vkW+f_ZwCNwJ=SHBy$nD?Bt` zoIsD{ROKple;+Qi4>;IU$*D#5-45Bi3~>w-!Ui&`Zdd9Hk#uDlx<+!{-0sMpDQ24PV&NUU>*7e?(PaPpW6n&)LL}Dkk`WU!`{R)W zGv&mfh3}qRGZE35T*KV)N;W?<62ALFVz^}3eIdahh3{rLM=$C+!mbRjN2ZSy2@@%9 zo7stJ*vgC;VQHZPFT7P@N{TnD>;S9q3rB3^JA z4aX&SQZhGBl9MKJOp2de$-U51J9#U|IeRWA$8g-&()E=4xqfLW|0s8|=R2%k7JZZ}~uC1p$<5@lD>iIOQG_2^*5ArvL;=IcTfEy}$1=hP~Xx!*_ z_qHKf*77ajhHh?CR~OrYy_!k0^Jk0gU(4d_hHe)>EQ?)TT`m*w;J9vA?j`WfF|IS0 zfd?kI`mV?#zIUt_NPedj>~wt(bjq@*`-beDt{Dn;xylF+VP6>`dxy&jw7}2h@xQMh zh;~F*)VH$OK`#VS!*-Q#Cw|D_iv5q@o(@b$1NQG#Kj41jn=ZZA&*OnKkXDtq0~!3g zfNx~H>9YI$12FvP_qPBKj$6>}`s%vB1FoNC@gjuK4cQB!5#O^6zmV_)i7m!Ynuay2 z_?5K{jkbEW$p2%6U)j*usM@4$1qXYcD9nzKdJAp&eMqf9!I6eF;me7m12dpis*1wr zKNc9RN~<(9XS%oqrv^GdpT?;H2Md;jgAH|q&PaqcuoKdmhY+7~1)Ej-W)+`Naj%L8 zR6L^MaUdOdmqX~61l>I1yb+-su(4~ie(A4wPfws8UYBJDz3OLR5;&HMBLt`2LXqzI z=x&d$^XLkXuI=cmj;`nEZcMok15&BMDV}aB>8g${Y3Sl5xWr;b{sOu0mpK1_zCG9H zzRGdM(#!Kw!f7fEq>NGKPnuJbHiqJoB-x;~LTi0nx=)$`I;toGE`K)D0x8Y9^!3c&KSo ztKWCueY@-DkQx4Nu6*{~@1Aq-J@?#m?t6RJe{^Txmb$t+;g)()FI?1x#$)RoNte*X zYSAj1#kFD;nkaMW9O@*$BDX)FEPJmLf^-#8P{{EIt`}k;>`<#uhzN+|QjV%fL7sw~ zG=Kp~lyi>AkFEIfr-g7SMSUfs{P!RSyQ0YP(YhV-+WC)QY?3>^w$nWyt`#jhLo~!Af@yMrupQw>P;*lQ)K2jt9YmfXNJotyePuJ*| zJ@OAccn$olVWB_4TIugkF~C&e`fv2$TRiwnz{hIlyBqka8r<*EPXY(85#kK&;Cy2q z`6IxO*Yy8WkNg=Ae!+wP)q{WJ!IwNZRF7N<31KUHJ_GC)uKy+v{u1!iS|K_iqx}}} z0x(io;$y()fIp}3JHWwfg^XkBe;P^1cwh;N|5{n+!R^4uYvjT(V&hR`Fqw{~l3z6y zj9TV+(h||pSSD`93=p_6nv9#7PSJPw9o_f!+}nF+kFjBcNG10xwe}4g!nq6^(xV32 z*o_T_k+Y()2aH(a0b?+lOkr3fZH^hkXf!RMl#^*pI5dKZ#jusgn$fr+ks$@>lFA$~ za;9aZ%uzGt7_oE5-F?P}PRE83E17EF(8(F0J(x93cLsMES@S?LXPH?enxYZT+OYDt zT3}W<3(Z!9P|U=PSSAg#tXo~N5wpfCDw$z3-RaC29Wps|CRT;9R3>e*9}I#Rg5hM$ z8p)bQDmsxFu|)R3{))&La(Y+VjQt~nHC+aiusb)FGY*(mk7E=4(7&s{r@QY?L;3;Y zSTtp}OFzhJR+;NeBOQY+iKgS(OfoLLI+jgZrn?VuGiPNp6PVWV4_1()-A+f&PS?8A zPmEB&m1W^Zr*`@Fx#OvA&YJOTbj-8GaM=UGbqqqoBOXah1mb=3tN{N z!>pO1{*mlpG-i4vvfJg#&RE^)cqEgvJnC69Wk#{sYWO1R#gI7^8=ml(WMwDh;P*sF zqlk|oBb6L7Q^^Af%SeyHfo60FDd1#`(LEaLl^K+Eg8Acia$Wdw`zc?4+-^=12*Zp= zttdiee-3`PqFD<~>9~_@gPH75)T(BDbQHmf)M~#~9RazqteJ}r4I>mgR1*?d)l~4g zs#ZL9^UVqjDK1YA93}A-8-)06(C0y|pkD{!)kNG3V$nfuARcSK0lE?NSx^x4TOd3R zcDw zaHPw!MsYG6`Jw@|8id!cO5x)MuQTFPAU;o;K^mwL#KPwW%MA{bpY$aQ9!uhCPz#9f zQMZ8b>L-2;#Inv|_~a};QHMWT=aYYRvc(6?w^m9W?w2*bX;MbSUM_KsPGJk%YyYbF zl~wmk`>3;>A1Ku92IszNpcgJ0aFtJRe^@9#3GT`}XQttrdhw!^I`Z#gTFO~|l$*f9 z{9?f|MZOc$$8W$9(wAjT{@_L5@J^BFCoQMve{LM2 zu|t#O@Wm84f_miyqhRGIeay0Ql+l`Cb?Mv+%F!S(#Y?9pW~3(AxU?WKqddWnrQ;Ga zf)g6?5s6t%z;dxPFR@OH*~QY7#1tiNBOaC*#*4&e;sJ@Nn%GJlk(j4NB0{`bVs=gJ zChm}U6Y&7CCh=zC1aVN}UgBY5Au)$ej1wD5_77=9Pw$1cM#7LFG$R( z6OR)gmv}Gn5#l2f4-h|1JTGya_&D*D#567OBJr@qDdGj<0f}j9;yc64~?9J0zYYK1ZxcJVjh44oW;jyi6=4o+G|Uy!^jh|D(jnUaTJ&K$iaUgW%JpH#H%O zalKs3&jS~F^#;3F*A{zqKh+9;z5Gw;1&!uCrQ$c)R(yq@{pH*7IbAxa$^IA1M;*0> zJ*D}gbpzx)?WRjByocU(=2fGXKm0k3ELV)AX47*iJ@V?0eT6o?yhQn-_bZ)l zh1_0i-!ob0(}VW*am?7r`MF|jnBQK_`Ag`9MK3nZ{T1z6Wk<&Ct+I>K*J&^MX^OKu zaWi)sJj4cDu8?6+24-; zMLs+*?v#ca79il@rGG?#7N3`o%VNF*9^gdSOuh^h^Na8t*HdoBlr{YD&9cqg-QP?Nw2dNXF|ADBZ<~Xxck+4Zmf|o@T>E*S0yrAXw^+3i64`vk`;OzQDMN>9jf8))VC{|B>toB3DRq?g|#Vy6{b%q}#|e4W0? z%XP{0`Zvf6tEl-yYQl$2Gk;DU8Rg2e%#={^9t}~|jBwsQ$-PEfuo%(oJRJ+rfIw`d zy|x_qNe;L63wKOfyNdZg_{e~H??Y_tH8M_U#BTIb8-^9zSh0--;vh-D3n}M(D(3ey z8lJOzb&H0857T*@&=+ZR(n=S5bsg5oG?%6#HN#Rmeh2KoW1LHPmC4og-0M(ic=bPg z(D%!DkHzZ$OC(^e4$JMfkLi3i6esm&Y_6Z4c@~bs>P|Jywee7y(WjsQVS{o( z8(r{cP=Y%;u!{%Zf!uzEv3>qJX%_``mfm*LGR8r@>EHrY4)JV77&XFcA)0I7>6}eI zj<`!rJDx=M5Hjpr_8}QtA-()%DI58QeMrWW4^o~z<$f%ZgP{93t5RI>tv@rn;Dln+ zWB*(TR>z4!X=xvw={%+K&wix;Ui@#2S4o%8p(A~B27LB|7qJ@Abnh}FWlv$52b|iE z!yh?b0^?D?uq+QmsgLI$7KY~^-Wee)({Iu*y?UE+b}QHBhZf{~x~hZG+0Xv++puT4 z6n6SwEPvU_>gx0LWhoC-$8S~RY8(eyQwrd-;)_h#nU%h8t;g-t(_aYH1~pN+kLnK!ws0I=*X)Z+?#3Y zwx}xM4G~(=wc1wEcCJjW97WxRRD1C9N4ugGH$j?n%Tt{fS5C?F0gYE}W?b~+I;aLl zoa-kn3BxIsEmBjnGFrtfbvNf+TU@EfX~*D{%$Ua87cHl*U!3oh49~i+ZWncv4RzNw z`u%grW1cvqM?Y9ukx%*VKttYlZ$R4~Xy^|3OO$N_HA2p}ZQ;uISLJ)r+y=S)POId> zyB&EDvPn<`a(OtT&bMa1F>Xa0@B2CCtgk!J_;7u9Ao!5KI}plWb#EY2f23|rAk-5G zmLNL?rA1hD@~*3WJJ?(b1iMcK8oL)U(aC^+%hmD@qKx4N5TZW;=eQ4X++_IzLH>*U%Km4S9AEva{dYV4 zxtA9p??wNEl=lWU%k}MgxPIrX&$rWAA0?vhy818j{hs^Nfs?cauUf<4o51J6e-7F6 z;Cq1=!1>*z1LxLvz%}^#0=R&0_#JB=M~wx38vHQ$GWhf08tga=E+_oe$FDr_|JVal zP1VKYNhdDc;@splAYXasxr{?61kO80(mXKl5C=fKGv#X~(k;V$<#&q^`th9NS&M5P zF@tYqxXuxO3yy!H4R=>ik#}_DooFQIog4W{5P7d^->G;)@vP!giWe1sO!2<}$2FL| z59A#vf5YbaL_6_{Arinm!=P5Vviz%h{tVKF#zWIK+QOb0NI zjtqVEam(Qj=Xc(D@Hx;Epd+BCLB~K(fgT6dmH;lYuLp%ew}ZBU_JPu%2SEoxPlBEW zy$pI2^fu_Hpi7_toN+xU47we(4YUuG20aKm2znCqEa+v>o1nKrKLuR^1rQ%IwCS#% zp5M`0@7lKWCao*HQS0njkAEE-wbtRRseTl}^`G=Bwpel~ivyWdTw5PrFP9b0CGfk4 z72Pkw@riV9V#wiER)iDLTtb8oq({P|W;U11q%Q+T{Bnp6n3IDzmEn{n!uU0Y%K%cVmM_4a z;}E)Xb|I|;F?OlP_$H-MclFp#3WFH)tTX;e%pqdxx+eTC>dXnYGcQO3s1ws}SFaCs z#su4Wevk%Gcju)Z^K&Q2)mw%RiRTdY*zW3m1$DOZyt#;jhoo_ERj5bW2Xgf^#4%}# z6R8qZJPuAhzWW@(p-1XugDM=0Fr_%BI*TtIq!!P-5Q)(mz1sf+Lm3OlW0?WLB4L3Uxu9H pK}8no-Jt4Zl#v!e{H@C^$2BKTUF^>5+UB;u0ll|f08Tl}{{igEAv*v7 literal 9568 zcmeHNe{ft?6~5Ul?b^o8f-6Lb@~BTprCrmIVu7d$*_I%YhNh)uTKlrizIIpkM`rgW zB+QBlo3!0|UCgvqnXzhz)&Z^KbZB*CbV|zF24@hfu zS&Ae6tG7LS?sw0*=bn4+x%a)jo6oK7Tvt_9C7e<%s)d7^(Rla`ydB=OOf``x&iX?@gIk6l4HNJBSfWg6>4y?REXbSLP8L4tHALJ zoCWTw72C<8gZh4p!g?DsZ9#f35-_tiWHdz|R2tFz`*_ z^#4qS{3YN_nZHvN@;?H%`h_@V&;N}Ixx3n#9qkKoE%5Anh4?V)9B)0aa|lh{tSv2Qd9(@rggp-C2R}1wL9P7n;^P7}9n|6QNl2Vcmuy zLm!M9A~c}wOzFDT7wUabH$>;%cedT%zCO6NU0c0c#G*Sw1HH+x-n(-3>OeZlhQxq| zHVtiT(6qFHo?36@L2YL!8iPHlPdWN&1JSUaY!anTX(N>!5|%Kf^%{ev4!yBtT6f&B z{MfYd?oMrWljTOg5sj@}-NZqq&!M=kp-(~=>E2LGw~VD9O3UfACli>SoKE-V?)J9M zwVE^oIh9vRGe*1*%CG~2ebHW{Kcyo`v6vX@XHYDGHJfCgo@kmMmk|@GT{}uzT0(!w z31O*S+tI&szE3C?OZJL*Km1~2+`@fEB&CPKnruhVtoYdB+NB%XytOpfZr)Q0WmB1Q zZ+|MKCk)%Ig|;#JgVN`4G-V74XTk%>i7?Ds^OR0$y;k&Y5e%mcSVro7 z;Q;*!IRxewOYYLra4)70=<^wY{0rM@kt&-29 zo-Q0qHrZJuy+kyeH815FrzGNfytfb2T@Ys@H6(rgWN09S*~Yb4G_J>@yCQ~`7{C(f zp*WJH}mG5cD7e^*hfYBp_HBu#rv?~8to>eh9fk>y*J-% zgSgG*nSw(m9Ag8@hd{T26wq?e3J?okS40g+1>u!N@VHwB!fT3rUw9Yz`#>u}PWcEb zw}7s<8*qe+Rd(HBJdli`Se z+j1o&cwG@*5FQ(N2mJR1j|y4p@yU0-r52RW4_5K$knc>4#ca~IwntMF;PrMhL^!u+g4JBe?YMC7cwL7bsWa;u$vS*UTjf6p%Z4Bo`7a*xfa zcRT7nkOKP7B@KhQN;yFJ=`wlK=m^4AEl#41g>nyuWq+1mr-$H}Tl~|4@_m?de*uX> ze$M7UxA{vpKX3CXo4;c7qRnS)K5O$qBqQxHDHn4@awLI=9RAdhL%~OmMD>#+nd`{0 z`1RyibcGxVt&(GL8^{&x0rF)qPmV=xCdbRd8uA9b+mmBKTgh(_B1n#n(m{^o?T>LbSnG03qpb0?VTb4TRdWsN&D z0us|xb0|lA#1v1Tlb92ZlMDe0A%9+sG* z$QsJ~B!<%>(n{PTF&q_<4&p9}xsxMZ#H|vuYh(*?qr@%5J;aK{t;7*xpTt4pK4Kv; zO-BZaXWvA_kxt@a;wgzaRAevlIf*wCj}uQw%%LNX6Cam&8}VV{!xHxpKSew)ahUix z@vy{nE%H6$K8a(*6U03d)78lH#9a~_#OH`xC8q0RgW1|mC2y$rdH0a7XR?Z+#EFOwifc=Wn2DPes&i>j?a<8 zFBRGUQt=m-nrCZaJa60xIZyhL!YNDc*oVDSC41v`G3(XB{oTb^5&4lq-s&)8o_Gn^ zTr@Ue)X|MEp_{pgwXdKy$#YPcC(b)MUP=y>Nv0^ddhBP0-^{*(I_23id2zmHvs#Rn zNoM9H=<|_zNp{w@a}U}O693linCwaHV8$Z=;Yi`d>#ca2S?RtoyE3_r2x{vfV(XrC zs|-u=TFU@p>hy=XleukbOU_VRi!Wo5MhaP&%4O7GE~a*v8Fk~Ti^VTujN2|5*XCwT z=$g=lAZS7X{W}Z!0b!P$Jo`EVkbRAlX&8I8cC-m6n3=sq>>2w}?dTHX12POUK<0-{ z4~FvR*DB^QIuD2I%(cR7&Ez^&H;^yirpOG*GVI8a8qVAlnBWSAH;fT;r`ZJ!w{>J{ z$97XLeVC-GP&Q%xl*<)w}a2KnfI{l*5JG4-*OzHeT87XIRzYR3~Wpt5wY)422 zWcp*6w!Pk)k+3aT#gW2!Tx%fu=PV25JLe!Q!83-u_&DmtBUS@X$KsQynE zrom2ju9UC;-{p38l&w=Pd*$WyPhkuzvz|n!3y1RA=MlsUzsP4_CjUu3yBysvOy;u> zkUyKdnE!!%E#1U~S&E2_6prCt6}}xbzoqib&1cj)>^pa^MJ>AQ;MDGIVK7J4VJJZ8 zk?{*!+w4`0YzEavY=OuA0=apNt9Ic!D_8XhGU+TYT>0c_|3`QBKpma0df$|D>{UNesI4y^MU z?2!yop;_g|;ne2FXYeMPJA?5%PfxjWD^z+>{4p$GLo+iheYDo2k35m*_SfJPK#zJn zC02}0V35YzFHB&JW;`m^z?GH)p8tp=p8t-&cfjJlfb9%tWWrvQC1w;$gJN>ktj7I?pNyc^d+(Znu=cUO8? z+@uszuy3ny+e(3l7-9Zk13E6x=}NhB{&aJ8n`cPOGH;S~~E z(PO@?q;1_cm2^w(_ASkP59!ZaT5(mNShrxMao5QSDMnJ6H)dV&S(h2kq!1*&g|k#W zyg^iDJXP1#xZQiP$#`m#G8gCO^kbLK|=wfpS67Z z0d!x0?iz5%zOvJ@PoK|&W+3NXwFr8JaOft7+xaPk5yq+M|oOT)^UjzALlvmibmMXj^ZKDdyH59GB-Dcx{4P` zftR8n_ylkSoZl@B@MiF4Z!DHYX!S{i`0)7(Q z55Im6&hh_W{vUz=)d)1!&d(fAK=B&xrg*f?f_NV|gTspFKJOYy`+#{L*aPBSCtn(H zge7?{F9P{-cJO@TnT;zTVYsck(PzN%Uo_$#3o7!yj=UR<|M1Eo`ha=fL9KMp$CrCtO9>na{ApC+6*PGi4LBN- zK1>3~j%>$ql3W3d<08ozz0)$>X#Ea+2>dW;9CQ#g3fc$S3(A1X%byWY7cP>o0xbhI zf$jut0PO&!K#zj~ zz$!VFKstgSNQ}@95eN?@(nE2J8z~Wpgwhca*p=uH4CtwJG?`ccwD3ZR4j7Y0tV&{UW}83Eqnx|QOg*9 z!4Lub))U7MWFnAA8oG3qlUwQdi%I{7g?oX-J;l92>Om3_Gv}OqasA0xv!|ly+kq~bmZ(px&g#|r5>ATVxF_koF{SL5L4H2;bW+CAF!Q! zfz*RKG5vP*I#Fj%u$^ZGsRwmuTOS#%w+6 zZZFe2hdd@ta3H(*Y#s(@cm8HGjsuQVRc4P>k`pV=;~>6xkhT!mg=4Y|fT&MV`QN<& z%Hrsa0`aJ2tmNO<;P5GPuN1#=$l 0) { + // Sleep until the frame should be rendered + try { + Thread.sleep(diff); + } catch (InterruptedException e) { + return; + } } - if (!AvcDecoder.getCurrentFrame(frameBuffer, frameBuffer.length)) - continue; - - // Draw the new bitmap to the canvas - Canvas c = renderTarget.lockCanvas(null); - c.drawBitmap(frameBuffer, 0, width, 0, 0, width, height, false, null); - renderTarget.unlockCanvasAndPost(c); + nextFrameTime = computePresentationTimeMs(frameRateTarget); + AvcDecoder.redraw(renderTarget); } } }; rendererThread.start(); } + + private long computePresentationTimeMs(int frameRate) { + return System.currentTimeMillis() + (1000 / frameRate); + } @Override public void stop() {