From b0bb8b685cd855644ef547a8a7bff8de814c3861 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 29 Nov 2013 21:06:35 -0600 Subject: [PATCH] Further optimize the JNI code for faster H264 decoding. Add an experimental RenderScript renderer. --- jni/nv_avc_dec/nv_avc_dec.c | 312 +++++++++++------- jni/nv_avc_dec/nv_avc_dec.h | 9 +- jni/nv_avc_dec/nv_avc_dec_jni.c | 61 +++- libs/armeabi-v7a/libnv_avc_dec.so | Bin 17700 -> 17700 bytes libs/x86/libnv_avc_dec.so | Bin 9576 -> 13664 bytes src/com/limelight/nvstream/NvConnection.java | 2 +- src/com/limelight/nvstream/NvVideoStream.java | 11 +- .../nvstream/av/video/DecoderRenderer.java | 3 +- .../av/video/MediaCodecDecoderRenderer.java | 3 +- .../av/video/{ => cpu}/AvcDecoder.java | 15 +- .../video/{ => cpu}/CpuDecoderRenderer.java | 36 +- .../nvstream/av/video/cpu/RsRenderer.java | 36 ++ 12 files changed, 347 insertions(+), 141 deletions(-) rename src/com/limelight/nvstream/av/video/{ => cpu}/AvcDecoder.java (68%) rename src/com/limelight/nvstream/av/video/{ => cpu}/CpuDecoderRenderer.java (82%) create mode 100644 src/com/limelight/nvstream/av/video/cpu/RsRenderer.java diff --git a/jni/nv_avc_dec/nv_avc_dec.c b/jni/nv_avc_dec/nv_avc_dec.c index 4366ce11..3d392d7d 100644 --- a/jni/nv_avc_dec/nv_avc_dec.c +++ b/jni/nv_avc_dec/nv_avc_dec.c @@ -8,14 +8,20 @@ #include #include +// General decoder and renderer state +AVPacket pkt; AVCodec* decoder; AVCodecContext* decoder_ctx; AVFrame* yuv_frame; -AVFrame* rgb_frame; AVFrame* dec_frame; pthread_mutex_t mutex; + +// Color conversion and rendering +AVFrame* rgb_frame; char* rgb_frame_buf; struct SwsContext* scaler_ctx; +ANativeWindow* window; + #define RENDER_PIX_FMT AV_PIX_FMT_RGBA #define BYTES_PER_PIXEL 4 @@ -32,6 +38,8 @@ struct SwsContext* scaler_ctx; #define BILINEAR_FILTERING 0x10 // Uses a faster bilinear filtering with lower image quality #define FAST_BILINEAR_FILTERING 0x20 +// Disables color conversion (output is NV21) +#define NO_COLOR_CONVERSION 0x40 // This function must be called before // any other decoding functions @@ -44,6 +52,8 @@ int nv_avc_init(int width, int height, int perf_lvl, int thread_count) { // Initialize the avcodec library and register codecs av_log_set_level(AV_LOG_QUIET); avcodec_register_all(); + + av_init_packet(&pkt); decoder = avcodec_find_decoder(AV_CODEC_ID_H264); if (decoder == NULL) { @@ -98,54 +108,56 @@ int nv_avc_init(int width, int height, int perf_lvl, int thread_count) { "Couldn't allocate frame"); return -1; } + + if (!(perf_lvl & NO_COLOR_CONVERSION)) { + rgb_frame = av_frame_alloc(); + if (rgb_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", - "Couldn't allocate frame"); - return -1; - } + 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; + } - 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, + RENDER_PIX_FMT, + decoder_ctx->width, + decoder_ctx->height); + if (err < 0) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Couldn't fill picture"); + return err; + } - err = avpicture_fill((AVPicture*)rgb_frame, - rgb_frame_buf, - RENDER_PIX_FMT, - decoder_ctx->width, - decoder_ctx->height); - if (err < 0) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Couldn't fill picture"); - return err; - } + if (perf_lvl & FAST_BILINEAR_FILTERING) { + filtering = SWS_FAST_BILINEAR; + } + else if (perf_lvl & BILINEAR_FILTERING) { + filtering = SWS_BILINEAR; + } + else { + filtering = SWS_BICUBIC; + } - if (perf_lvl & FAST_BILINEAR_FILTERING) { - filtering = SWS_FAST_BILINEAR; - } - else if (perf_lvl & BILINEAR_FILTERING) { - filtering = SWS_BILINEAR; - } - else { - filtering = SWS_BICUBIC; - } - - scaler_ctx = sws_getContext(decoder_ctx->width, - decoder_ctx->height, - decoder_ctx->pix_fmt, - decoder_ctx->width, - decoder_ctx->height, - RENDER_PIX_FMT, - filtering, - NULL, NULL, NULL); - if (scaler_ctx == NULL) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Couldn't get scaler context"); - return -1; + scaler_ctx = sws_getContext(decoder_ctx->width, + decoder_ctx->height, + decoder_ctx->pix_fmt, + decoder_ctx->width, + decoder_ctx->height, + RENDER_PIX_FMT, + filtering, + NULL, NULL, NULL); + if (scaler_ctx == NULL) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Couldn't get scaler context"); + return -1; + } } return 0; @@ -179,14 +191,15 @@ void nv_avc_destroy(void) { av_free(rgb_frame_buf); rgb_frame_buf = NULL; } + if (window) { + ANativeWindow_release(window); + window = NULL; + } pthread_mutex_destroy(&mutex); } -void nv_avc_redraw(JNIEnv *env, jobject surface) { - ANativeWindow* window; - ANativeWindow_Buffer buffer; - AVFrame *our_yuv_frame; - int err; +static AVFrame* dequeue_new_frame(void) { + AVFrame *our_yuv_frame = NULL; pthread_mutex_lock(&mutex); @@ -196,77 +209,149 @@ void nv_avc_redraw(JNIEnv *env, jobject surface) { // responsible for freeing it when we're done our_yuv_frame = yuv_frame; yuv_frame = NULL; + } - // The remaining processing can be done without the mutex - pthread_mutex_unlock(&mutex); + pthread_mutex_unlock(&mutex); - // Convert the YUV image to RGB - err = sws_scale(scaler_ctx, - our_yuv_frame->data, - our_yuv_frame->linesize, - 0, - decoder_ctx->height, - rgb_frame->data, - rgb_frame->linesize); - if (err != decoder_ctx->height) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Scaling failed"); - goto free_frame_and_return; - } + return our_yuv_frame; +} - window = ANativeWindow_fromSurface(env, surface); - if (window == NULL) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Failed to get window from surface"); - goto free_frame_and_return; - } +static int update_rgb_frame(void) { + AVFrame *our_yuv_frame; + int err; + our_yuv_frame = dequeue_new_frame(); + if (our_yuv_frame == NULL) { + return 0; + } + + // Convert the YUV image to RGB + err = sws_scale(scaler_ctx, + our_yuv_frame->data, + our_yuv_frame->linesize, + 0, + decoder_ctx->height, + rgb_frame->data, + rgb_frame->linesize); + + av_frame_free(&our_yuv_frame); + + if (err != decoder_ctx->height) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Scaling failed"); + return 0; + } + + return 1; +} + +static int render_rgb_to_buffer(char* buffer, int size) { + int err; + + // Draw the frame to the buffer + err = avpicture_layout((AVPicture*)rgb_frame, + RENDER_PIX_FMT, + decoder_ctx->width, + decoder_ctx->height, + buffer, + size); + if (err < 0) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Picture fill failed"); + return 0; + } + + return 1; +} + +int nv_avc_get_raw_frame(char* buffer, int size) { + AVFrame *our_yuv_frame; + int err; + + our_yuv_frame = dequeue_new_frame(); + if (our_yuv_frame == NULL) { + return 0; + } + + err = avpicture_layout((AVPicture*)our_yuv_frame, + decoder_ctx->pix_fmt, + decoder_ctx->width, + decoder_ctx->height, + buffer, + size); + + av_frame_free(&our_yuv_frame); + + return (err >= 0); +} + +int nv_avc_get_rgb_frame(char* buffer, int size) { + return (update_rgb_frame() && render_rgb_to_buffer(buffer, size)); +} + +int nv_avc_set_render_target(JNIEnv *env, jobject surface) { + // Release the old window + if (window) { + ANativeWindow_release(window); + window = NULL; + } + + // If no new surface was supplied, we're done + if (surface == NULL) { + return 1; + } + + // Get a window from the surface + window = ANativeWindow_fromSurface(env, surface); + if (window == NULL) { + __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", + "Failed to get window from surface"); + return 0; + } + + return 1; +} + +int nv_avc_redraw(void) { + ANativeWindow_Buffer buffer; + int ret = 0; + + // Check if there's a new frame + if (update_rgb_frame()) { // 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, + if (render_rgb_to_buffer(buffer.bits, decoder_ctx->width * decoder_ctx->height * - BYTES_PER_PIXEL); - if (err < 0) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Picture fill failed"); + BYTES_PER_PIXEL)) { + // A new frame will be drawn + ret = 1; } // Draw the frame to the surface ANativeWindow_unlockAndPost(window); } + } + + return ret; +} - ANativeWindow_release(window); - - free_frame_and_return: - av_frame_free(&our_yuv_frame); - } - else { - pthread_mutex_unlock(&mutex); - } +int nv_avc_get_input_padding_size(void) { + return FF_INPUT_BUFFER_PADDING_SIZE; } // packets must be decoded in order +// indata must be inlen + FF_INPUT_BUFFER_PADDING_SIZE in length int nv_avc_decode(unsigned char* indata, int inlen) { int err; - AVPacket pkt; - int got_pic; + int got_pic = 0; - err = av_new_packet(&pkt, inlen); - if (err < 0) { - __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", - "Failed to allocate packet"); - return err; - } - - memcpy(pkt.data, indata, inlen); + pkt.data = indata; + pkt.size = inlen; while (pkt.size > 0) { + got_pic = 0; err = avcodec_decode_video2( decoder_ctx, dec_frame, @@ -275,29 +360,28 @@ int nv_avc_decode(unsigned char* indata, int inlen) { if (err < 0) { __android_log_write(ANDROID_LOG_ERROR, "NVAVCDEC", "Decode failed"); - pthread_mutex_unlock(&mutex); + got_pic = 0; break; } - if (got_pic) { - pthread_mutex_lock(&mutex); - - // Only clone this frame if the last frame was taken. - // This saves on extra copies for frames that don't get - // rendered. - if (yuv_frame == NULL) { - // Clone a new frame - yuv_frame = av_frame_clone(dec_frame); - } - - pthread_mutex_unlock(&mutex); - } - pkt.size -= err; pkt.data += err; } + + // Only copy the picture at the end of decoding the packet + if (got_pic) { + pthread_mutex_lock(&mutex); - av_free_packet(&pkt); + // Only clone this frame if the last frame was taken. + // This saves on extra copies for frames that don't get + // rendered. + if (yuv_frame == NULL) { + // Clone a new frame + yuv_frame = av_frame_clone(dec_frame); + } + + pthread_mutex_unlock(&mutex); + } return err < 0 ? err : 0; } diff --git a/jni/nv_avc_dec/nv_avc_dec.h b/jni/nv_avc_dec/nv_avc_dec.h index bc47b021..b67bc11c 100644 --- a/jni/nv_avc_dec/nv_avc_dec.h +++ b/jni/nv_avc_dec/nv_avc_dec.h @@ -2,5 +2,12 @@ int nv_avc_init(int width, int height, int perf_lvl, int thread_count); void nv_avc_destroy(void); -void nv_avc_redraw(JNIEnv *env, jobject surface); + +int nv_avc_get_raw_frame(char* buffer, int size); + +int nv_avc_get_rgb_frame(char* buffer, int size); +int nv_avc_set_render_target(JNIEnv *env, jobject surface); +int nv_avc_redraw(void); + +int nv_avc_get_input_padding_size(void); 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 70f165b7..7b38f5d4 100644 --- a/jni/nv_avc_dec/nv_avc_dec_jni.c +++ b/jni/nv_avc_dec/nv_avc_dec_jni.c @@ -6,7 +6,7 @@ // 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, +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_init(JNIEnv *env, jobject this, jint width, jint height, jint perflvl, jint threadcount) { return nv_avc_init(width, height, perflvl, threadcount); @@ -15,20 +15,69 @@ Java_com_limelight_nvstream_av_video_AvcDecoder_init(JNIEnv *env, jobject this, // This function must be called after // decoding is finished JNIEXPORT void JNICALL -Java_com_limelight_nvstream_av_video_AvcDecoder_destroy(JNIEnv *env, jobject this) { +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_destroy(JNIEnv *env, jobject this) { nv_avc_destroy(); } +// fills the output buffer with a raw YUV frame +JNIEXPORT jboolean JNICALL +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_getRawFrame( + JNIEnv *env, jobject this, // JNI parameters + jbyteArray outdata, jint outlen) // Output data +{ + jint ret; + jbyte* jni_output_data; + + jni_output_data = (*env)->GetByteArrayElements(env, outdata, 0); + + ret = nv_avc_get_raw_frame(jni_output_data, outlen); + + (*env)->ReleaseByteArrayElements(env, outdata, jni_output_data, 0); + + return ret != 0 ? JNI_TRUE : JNI_FALSE; +} + +// fills the output buffer with an RGB frame +JNIEXPORT jboolean JNICALL +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_getRgbFrame( + JNIEnv *env, jobject this, // JNI parameters + jbyteArray outdata, jint outlen) // Output data +{ + jint ret; + jbyte* jni_output_data; + + jni_output_data = (*env)->GetByteArrayElements(env, outdata, 0); + + ret = nv_avc_get_rgb_frame(jni_output_data, outlen); + + (*env)->ReleaseByteArrayElements(env, outdata, jni_output_data, 0); + + return ret != 0 ? JNI_TRUE : JNI_FALSE; +} + +// This function sets the rendering target for redraw +JNIEXPORT jboolean JNICALL +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_setRenderTarget(JNIEnv *env, jobject this, jobject surface) { + return nv_avc_set_render_target(env, surface) != 0 ? JNI_TRUE : JNI_FALSE; +} + // 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); +JNIEXPORT jboolean JNICALL +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_redraw(JNIEnv *env, jobject this) { + return nv_avc_redraw() != 0 ? JNI_TRUE : JNI_FALSE; +} + +// This function returns the required input buffer padding +JNIEXPORT jint JNICALL +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_getInputPaddingSize(JNIEnv *env, jobject this) { + return nv_avc_get_input_padding_size(); } // packets must be decoded in order +// the input buffer must have proper padding // returns 0 on success, < 0 on error JNIEXPORT jint JNICALL -Java_com_limelight_nvstream_av_video_AvcDecoder_decode( +Java_com_limelight_nvstream_av_video_cpu_AvcDecoder_decode( JNIEnv *env, jobject this, // JNI parameters jbyteArray indata, jint inoff, jint inlen) { diff --git a/libs/armeabi-v7a/libnv_avc_dec.so b/libs/armeabi-v7a/libnv_avc_dec.so index f9e6b5bc1e60743afe815631a4a4a7f4fe077417..0b7e86b092de057148d3484c069f63caa6c62a9a 100644 GIT binary patch delta 6868 zcmZ3|#ki!4aYBmiOEU%r5N2gyU|`|;9~G*WMEh+#lY}|5u!dEO??&`znU=~Y8uqQ31|xDqw%+(@lT`iZ=&&E zOg_paU(X1NH>gFRa1vl*VBiQ~U3u$koam$3=BC)d=n-H zhPg5f3|qji=3;P0Gd~cOFT@auD!`CA*_Bzoekv0KLkW_xbC?(y0%aK(QlQ3eWMW|0 zD$Bqi1LYrMVqmx^3vo6mHtwUD`jd%)K~au@Aq1+Pof+z9FrR@z3YA~aV8+bAP$~yW zFCYU$m>C!<p8;*K;w{urM&Jlm}-$aQZsH!oaXoo`Hc6s^K>a1H*A71D#nJ7*5MGFjzq4 z>%bWo;!7n428L;@3=Aen>2WP91H%_xh&;%HCs-L6p1}C^3|tHkSs55u^biVI*%%l^ zkoXF03=DIS_}1W(0H&US!HJE5;esv$g9y|?A!zbZYzz!1lo=RYpz>L43=AeJ5Pm(# zMO8=|6c`v7`q23EK^EvUFic^F8QO>jUURv!0-Y|J`Igu%E7?EqtC#g0xEhzE}94v zfEu`(gMq;SqyWl4gvP(l!N3rLB>x(X|CfV-A;p-1!2zmYfRlk?i7^9%4wSFX$-r>I zn1R6;mWtgu85lTB5J4Qr$-p3h#4qJ!U|<22Rp3hM#QztjD%elnIC+4LTkG%FnK+@ zC|_b(d}dx|Nqj+Ka&~IT?zA zB%>%bF(n@C`uLR8;*z5LN`~^xyp;TM252y(rW{+Wlk=RHmnvc!Hc9kO+2haAs91$Ud-gQErFHIouQ2a#M4Y3o1AN=03<=&%lt#z`&5r z0Lc_l3=9mWkTMibf=cfsXcLH?fq}uDfq@~Efq@|ot^#5Pi0cd@7$CJk1_J{_7?cm9 z+!z=b6d4#8d>I%RvKSZ`v_XQP<_n0{oGi#FoU6gWz+lG!skO|(SqhRjLDj52NEsBH zF)%O$F)%QALHR}u3=GMj78aBbqPQ3s7@`>%7+}p9YE`0|P@00|Nu7x)o$#U`S_RU`S

sg06~$7XE-K%2&$M$K^1a?iy$b6 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 CgKEYA delta 5888 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 diff --git a/libs/x86/libnv_avc_dec.so b/libs/x86/libnv_avc_dec.so index affff12fc086536ab6452e9d7e5dcf231dec19cb..af38168ef7b491b710b18b823b1af4a018d28d1e 100644 GIT binary patch literal 13664 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 literal 9576 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 S2Er#47#Qw>7*Gt72QdND`qSh9 diff --git a/src/com/limelight/nvstream/NvConnection.java b/src/com/limelight/nvstream/NvConnection.java index 9619840b..5f044722 100644 --- a/src/com/limelight/nvstream/NvConnection.java +++ b/src/com/limelight/nvstream/NvConnection.java @@ -150,7 +150,7 @@ public class NvConnection { private boolean startVideoStream() throws IOException { videoStream = new NvVideoStream(hostAddr, listener, controlStream); - videoStream.startVideoStream(video, drFlags); + videoStream.startVideoStream(activity, video, drFlags); return true; } diff --git a/src/com/limelight/nvstream/NvVideoStream.java b/src/com/limelight/nvstream/NvVideoStream.java index e8b306ad..801bca52 100644 --- a/src/com/limelight/nvstream/NvVideoStream.java +++ b/src/com/limelight/nvstream/NvVideoStream.java @@ -17,10 +17,11 @@ import com.limelight.nvstream.av.AvRtpPacket; import com.limelight.nvstream.av.ConnectionStatusListener; import com.limelight.nvstream.av.video.AvVideoDepacketizer; import com.limelight.nvstream.av.video.AvVideoPacket; -import com.limelight.nvstream.av.video.CpuDecoderRenderer; import com.limelight.nvstream.av.video.DecoderRenderer; import com.limelight.nvstream.av.video.MediaCodecDecoderRenderer; +import com.limelight.nvstream.av.video.cpu.CpuDecoderRenderer; +import android.content.Context; import android.os.Build; import android.view.SurfaceHolder; @@ -129,7 +130,7 @@ public class NvVideoStream { rtp = new DatagramSocket(RTP_PORT); } - public void setupDecoderRenderer(SurfaceHolder renderTarget, int drFlags) { + public void setupDecoderRenderer(Context context, SurfaceHolder renderTarget, int drFlags) { if (Build.HARDWARE.equals("goldfish")) { // Emulator - don't render video (it's slow!) decrend = null; @@ -144,14 +145,14 @@ public class NvVideoStream { } if (decrend != null) { - decrend.setup(1280, 720, renderTarget, drFlags); + decrend.setup(context, 1280, 720, renderTarget, drFlags); } } - public void startVideoStream(final SurfaceHolder surface, int drFlags) throws IOException + public void startVideoStream(Context context, SurfaceHolder surface, int drFlags) throws IOException { // Setup the decoder and renderer - setupDecoderRenderer(surface, drFlags); + setupDecoderRenderer(context, surface, drFlags); // Open RTP sockets and start session setupRtpSession(); diff --git a/src/com/limelight/nvstream/av/video/DecoderRenderer.java b/src/com/limelight/nvstream/av/video/DecoderRenderer.java index 25a23e1d..486d0f18 100644 --- a/src/com/limelight/nvstream/av/video/DecoderRenderer.java +++ b/src/com/limelight/nvstream/av/video/DecoderRenderer.java @@ -2,12 +2,13 @@ package com.limelight.nvstream.av.video; import com.limelight.nvstream.av.AvDecodeUnit; +import android.content.Context; import android.view.SurfaceHolder; public interface DecoderRenderer { public static int FLAG_PREFER_QUALITY = 0x1; - public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags); + public void setup(Context context, int width, int height, SurfaceHolder renderTarget, int drFlags); public void start(); diff --git a/src/com/limelight/nvstream/av/video/MediaCodecDecoderRenderer.java b/src/com/limelight/nvstream/av/video/MediaCodecDecoderRenderer.java index babee793..c4226085 100644 --- a/src/com/limelight/nvstream/av/video/MediaCodecDecoderRenderer.java +++ b/src/com/limelight/nvstream/av/video/MediaCodecDecoderRenderer.java @@ -8,6 +8,7 @@ import com.limelight.nvstream.av.AvByteBufferDescriptor; import com.limelight.nvstream.av.AvDecodeUnit; import android.annotation.TargetApi; +import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; @@ -73,7 +74,7 @@ public class MediaCodecDecoderRenderer implements DecoderRenderer { } @Override - public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags) { + public void setup(Context context, int width, int height, SurfaceHolder renderTarget, int drFlags) { videoDecoder = MediaCodec.createByCodecName(findSafeDecoder().getName()); MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height); diff --git a/src/com/limelight/nvstream/av/video/AvcDecoder.java b/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java similarity index 68% rename from src/com/limelight/nvstream/av/video/AvcDecoder.java rename to src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java index c4299080..d9cbcf31 100644 --- a/src/com/limelight/nvstream/av/video/AvcDecoder.java +++ b/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java @@ -1,4 +1,4 @@ -package com.limelight.nvstream.av.video; +package com.limelight.nvstream.av.video.cpu; import android.view.Surface; @@ -27,9 +27,20 @@ public class AvcDecoder { public static final int BILINEAR_FILTERING = 0x10; /** Uses a faster bilinear filtering with lower image quality */ public static final int FAST_BILINEAR_FILTERING = 0x20; + /** Disables color conversion (output is NV21) */ + public static final int NO_COLOR_CONVERSION = 0x40; public static native int init(int width, int height, int perflvl, int threadcount); public static native void destroy(); - public static native void redraw(Surface surface); + + // Rendering API when NO_COLOR_CONVERSION == 0 + public static native boolean setRenderTarget(Surface surface); + public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); + public static native boolean redraw(); + + // Rendering API when NO_COLOR_CONVERSION == 1 + public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); + + public static native int getInputPaddingSize(); public static native int decode(byte[] indata, int inoff, int inlen); } diff --git a/src/com/limelight/nvstream/av/video/CpuDecoderRenderer.java b/src/com/limelight/nvstream/av/video/cpu/CpuDecoderRenderer.java similarity index 82% rename from src/com/limelight/nvstream/av/video/CpuDecoderRenderer.java rename to src/com/limelight/nvstream/av/video/cpu/CpuDecoderRenderer.java index b7d0eb30..a749c019 100644 --- a/src/com/limelight/nvstream/av/video/CpuDecoderRenderer.java +++ b/src/com/limelight/nvstream/av/video/cpu/CpuDecoderRenderer.java @@ -1,4 +1,4 @@ -package com.limelight.nvstream.av.video; +package com.limelight.nvstream.av.video.cpu; import java.io.BufferedReader; import java.io.File; @@ -6,19 +6,24 @@ import java.io.FileReader; import java.io.IOException; import java.nio.ByteBuffer; -import android.view.Surface; +import android.content.Context; import android.view.SurfaceHolder; import com.limelight.nvstream.av.AvByteBufferDescriptor; import com.limelight.nvstream.av.AvDecodeUnit; +import com.limelight.nvstream.av.video.DecoderRenderer; public class CpuDecoderRenderer implements DecoderRenderer { - private Surface renderTarget; - private ByteBuffer decoderBuffer; private Thread rendererThread; private int targetFps; + private static final int DECODER_BUFFER_SIZE = 92*1024; + private ByteBuffer decoderBuffer; + + private RsRenderer rsRenderer; + private byte[] frameBuffer; + // Only sleep if the difference is above this value private static final int WAIT_CEILING_MS = 8; @@ -76,8 +81,7 @@ public class CpuDecoderRenderer implements DecoderRenderer { } @Override - public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags) { - this.renderTarget = renderTarget.getSurface(); + public void setup(Context context, int width, int height, SurfaceHolder renderTarget, int drFlags) { this.targetFps = 30; int perfLevel = findOptimalPerformanceLevel(); @@ -111,6 +115,12 @@ public class CpuDecoderRenderer implements DecoderRenderer { break; } + // Create and initialize the RenderScript intrinsic we'll be using + rsRenderer = new RsRenderer(context, width, height, renderTarget.getSurface()); + + // Allocate the frame buffer that the RGBA frame will be copied into + frameBuffer = new byte[width*height*4]; + // If the user wants quality, we'll remove the low IQ flags if ((drFlags & DecoderRenderer.FLAG_PREFER_QUALITY) != 0) { // Make sure the loop filter is enabled @@ -127,7 +137,7 @@ public class CpuDecoderRenderer implements DecoderRenderer { throw new IllegalStateException("AVC decoder initialization failure: "+err); } - decoderBuffer = ByteBuffer.allocate(92*1024); + decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize()); System.out.println("Using software decoding (performance level: "+perfLevel+")"); } @@ -152,7 +162,9 @@ public class CpuDecoderRenderer implements DecoderRenderer { } nextFrameTime = computePresentationTimeMs(targetFps); - AvcDecoder.redraw(renderTarget); + if (AvcDecoder.getRgbFrame(frameBuffer, frameBuffer.length)) { + rsRenderer.render(frameBuffer); + } } } }; @@ -175,6 +187,10 @@ public class CpuDecoderRenderer implements DecoderRenderer { @Override public void release() { + if (rsRenderer != null) { + rsRenderer.release(); + } + AvcDecoder.destroy(); } @@ -183,7 +199,7 @@ public class CpuDecoderRenderer implements DecoderRenderer { byte[] data; // Use the reserved decoder buffer if this decode unit will fit - if (decodeUnit.getDataLength() <= decoderBuffer.limit()) { + if (decodeUnit.getDataLength() <= DECODER_BUFFER_SIZE) { decoderBuffer.clear(); for (AvByteBufferDescriptor bbd : decodeUnit.getBufferList()) { @@ -193,7 +209,7 @@ public class CpuDecoderRenderer implements DecoderRenderer { data = decoderBuffer.array(); } else { - data = new byte[decodeUnit.getDataLength()]; + data = new byte[decodeUnit.getDataLength()+AvcDecoder.getInputPaddingSize()]; int offset = 0; for (AvByteBufferDescriptor bbd : decodeUnit.getBufferList()) { diff --git a/src/com/limelight/nvstream/av/video/cpu/RsRenderer.java b/src/com/limelight/nvstream/av/video/cpu/RsRenderer.java new file mode 100644 index 00000000..d461e13d --- /dev/null +++ b/src/com/limelight/nvstream/av/video/cpu/RsRenderer.java @@ -0,0 +1,36 @@ +package com.limelight.nvstream.av.video.cpu; + +import android.content.Context; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.Type; +import android.view.Surface; + +public class RsRenderer { + private RenderScript rs; + private Allocation renderBuffer; + + public RsRenderer(Context context, int width, int height, Surface renderTarget) { + rs = RenderScript.create(context); + + Type.Builder tb = new Type.Builder(rs, Element.RGBA_8888(rs)); + tb.setX(width); + tb.setY(height); + Type bufferType = tb.create(); + + renderBuffer = Allocation.createTyped(rs, bufferType, Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); + renderBuffer.setSurface(renderTarget); + } + + public void release() { + renderBuffer.setSurface(null); + renderBuffer.destroy(); + rs.destroy(); + } + + public void render(byte[] rgbData) { + renderBuffer.copyFrom(rgbData); + renderBuffer.ioSend(); + } +}