| /* |
| * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "video_render_android_surface_view.h" |
| #include "critical_section_wrapper.h" |
| #include "common_video/libyuv/include/libyuv.h" |
| #include "tick_util.h" |
| #ifdef ANDROID_NDK_8_OR_ABOVE |
| #include <android/bitmap.h> |
| #endif |
| |
| |
| #ifdef ANDROID_LOG |
| #include <stdio.h> |
| #include <android/log.h> |
| |
| #undef WEBRTC_TRACE |
| #define WEBRTC_TRACE(a,b,c,...) __android_log_print(ANDROID_LOG_DEBUG, "*WEBRTC*", __VA_ARGS__) |
| #else |
| #include "trace.h" |
| #endif |
| |
| namespace webrtc { |
| |
| AndroidSurfaceViewRenderer::AndroidSurfaceViewRenderer(const WebRtc_Word32 id, |
| const VideoRenderType videoRenderType, |
| void* window, |
| const bool fullscreen) |
| : |
| VideoRenderAndroid(id,videoRenderType,window,fullscreen), |
| _javaRenderObj(NULL), |
| _javaRenderClass(NULL) |
| { |
| } |
| |
| AndroidSurfaceViewRenderer::~AndroidSurfaceViewRenderer() |
| { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "AndroidSurfaceViewRenderer dtor"); |
| if(g_jvm) |
| { |
| // get the JNI env for this thread |
| bool isAttached = false; |
| JNIEnv* env = NULL; |
| if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) |
| { |
| // try to attach the thread and get the env |
| // Attach this thread to JVM |
| jint res = g_jvm->AttachCurrentThread(&env, NULL); |
| |
| // Get the JNI env for this thread |
| if ((res < 0) || !env) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); |
| env=NULL; |
| } |
| else |
| { |
| isAttached = true; |
| } |
| } |
| env->DeleteGlobalRef(_javaRenderObj); |
| env->DeleteGlobalRef(_javaRenderClass); |
| |
| if (isAttached) |
| { |
| if (g_jvm->DetachCurrentThread() < 0) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _id, "%s: Could not detach thread from JVM", __FUNCTION__); |
| } |
| } |
| } |
| } |
| |
| |
| WebRtc_Word32 |
| AndroidSurfaceViewRenderer::Init() |
| { |
| WEBRTC_TRACE(kTraceDebug, kTraceVideoRenderer, _id, "%s", __FUNCTION__); |
| if (!g_jvm) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "(%s): Not a valid Java VM pointer.", __FUNCTION__); |
| return -1; |
| } |
| if(!_ptrWindow) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _id, "(%s): No window have been provided.", __FUNCTION__); |
| return -1; |
| } |
| |
| // get the JNI env for this thread |
| bool isAttached = false; |
| JNIEnv* env = NULL; |
| if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) |
| { |
| // try to attach the thread and get the env |
| // Attach this thread to JVM |
| jint res = g_jvm->AttachCurrentThread(&env, NULL); |
| |
| // Get the JNI env for this thread |
| if ((res < 0) || !env) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); |
| return -1; |
| } |
| isAttached = true; |
| } |
| |
| // get the ViESurfaceRender class |
| jclass javaRenderClassLocal = env->FindClass("org/webrtc/videoengine/ViESurfaceRenderer"); |
| if (!javaRenderClassLocal) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not find ViESurfaceRenderer", __FUNCTION__); |
| return -1; |
| } |
| |
| // create a global reference to the class (to tell JNI that we are referencing it after this function has returned) |
| _javaRenderClass = reinterpret_cast<jclass>(env->NewGlobalRef(javaRenderClassLocal)); |
| if (!_javaRenderClass) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not create Java ViESurfaceRenderer class reference", __FUNCTION__); |
| return -1; |
| } |
| |
| // Delete local class ref, we only use the global ref |
| env->DeleteLocalRef(javaRenderClassLocal); |
| |
| // get the method ID for the constructor |
| jmethodID cid = env->GetMethodID(_javaRenderClass, "<init>", "(Landroid/view/SurfaceView;)V"); |
| if (cid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get constructor ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| |
| // construct the object |
| jobject javaRenderObjLocal = env->NewObject(_javaRenderClass, cid, _ptrWindow); |
| if (!javaRenderObjLocal) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not create Java Render", __FUNCTION__); |
| return -1; |
| } |
| |
| // create a reference to the object (to tell JNI that we are referencing it |
| // after this function has returned) |
| _javaRenderObj = env->NewGlobalRef(javaRenderObjLocal); |
| if (!_javaRenderObj) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not create Java SurfaceRender object reference", __FUNCTION__); |
| return -1; |
| } |
| |
| // Detach this thread if it was attached |
| if (isAttached) |
| { |
| if (g_jvm->DetachCurrentThread() < 0) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _id, "%s: Could not detach thread from JVM", __FUNCTION__); |
| } |
| } |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceVideoRenderer, _id, "%s done", __FUNCTION__); |
| return 0; |
| |
| } |
| AndroidStream* |
| AndroidSurfaceViewRenderer::CreateAndroidRenderChannel(WebRtc_Word32 streamId, |
| WebRtc_Word32 zOrder, |
| const float left, |
| const float top, |
| const float right, |
| const float bottom, |
| VideoRenderAndroid& renderer) |
| { |
| WEBRTC_TRACE(kTraceDebug, kTraceVideoRenderer, _id, "%s: Id %d", __FUNCTION__,streamId); |
| AndroidSurfaceViewChannel* stream=new AndroidSurfaceViewChannel(streamId,g_jvm,renderer,_javaRenderObj); |
| if(stream && stream->Init(zOrder,left,top,right,bottom)==0) |
| return stream; |
| else |
| delete stream; |
| return NULL; |
| } |
| |
| |
| |
| |
| |
| |
| AndroidSurfaceViewChannel::AndroidSurfaceViewChannel(WebRtc_UWord32 streamId,JavaVM* jvm,VideoRenderAndroid& renderer,jobject javaRenderObj) |
| : |
| _id(streamId), |
| _renderCritSect(*CriticalSectionWrapper::CreateCriticalSection()), |
| _renderer(renderer), |
| _jvm(jvm), |
| _javaRenderObj(javaRenderObj), |
| _bitmapWidth(0), |
| _bitmapHeight(0) |
| { |
| |
| } |
| AndroidSurfaceViewChannel::~AndroidSurfaceViewChannel() |
| { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "AndroidSurfaceViewChannel dtor"); |
| delete &_renderCritSect; |
| if(_jvm) |
| { |
| // get the JNI env for this thread |
| bool isAttached = false; |
| JNIEnv* env = NULL; |
| if (_jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) |
| { |
| // try to attach the thread and get the env |
| // Attach this thread to JVM |
| jint res = _jvm->AttachCurrentThread(&env, NULL); |
| |
| // Get the JNI env for this thread |
| if ((res < 0) || !env) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); |
| env=NULL; |
| } |
| else |
| { |
| isAttached = true; |
| } |
| } |
| |
| #ifdef ANDROID_NDK_8_OR_ABOVE |
| env->DeleteGlobalRef(_javaBitmapObj); |
| #else |
| env->DeleteGlobalRef(_javaByteBufferObj); |
| #endif |
| if (isAttached) |
| { |
| if (_jvm->DetachCurrentThread() < 0) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _id, "%s: Could not detach thread from JVM", __FUNCTION__); |
| } |
| } |
| } |
| } |
| |
| WebRtc_Word32 |
| AndroidSurfaceViewChannel::Init(WebRtc_Word32 /*zOrder*/, |
| const float left, |
| const float top, |
| const float right, |
| const float bottom) |
| { |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceVideoRenderer, _id, "%s: AndroidSurfaceViewChannel", __FUNCTION__); |
| if (!_jvm) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer,_id, "%s: Not a valid Java VM pointer", __FUNCTION__); |
| return -1; |
| } |
| |
| if((top>1 || top<0) || (right>1 || right<0) || (bottom>1 || bottom<0) || (left>1 || left<0)) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Wrong coordinates", |
| __FUNCTION__); |
| return -1; |
| } |
| |
| |
| // get the JNI env for this thread |
| bool isAttached = false; |
| JNIEnv* env = NULL; |
| if (_jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) |
| { |
| // try to attach the thread and get the env |
| // Attach this thread to JVM |
| jint res = _jvm->AttachCurrentThread(&env, NULL); |
| |
| // Get the JNI env for this thread |
| if ((res < 0) || !env) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Could not attach thread to JVM (%d, %p)", __FUNCTION__, res, env); |
| return -1; |
| } |
| isAttached = true; |
| } |
| |
| jclass javaRenderClass = env->FindClass("org/webrtc/videoengine/ViESurfaceRenderer"); |
| if (!javaRenderClass) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not find ViESurfaceRenderer", __FUNCTION__); |
| return -1; |
| } |
| #ifdef ANDROID_NDK_8_OR_ABOVE |
| // get the method ID for the CreateBitmap |
| _createBitmapCid = env->GetMethodID(_javaRenderClass, "CreateBitmap", "(II)Landroid/graphics/Bitmap;"); |
| if (_createBitmapCid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get CreateBitmap ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| // get the method ID for the DrawBitmap function |
| _drawBitmapCid = env->GetMethodID(_javaRenderClass, "DrawBitmap", "()V"); |
| if (_drawBitmapCid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get DrawBitmap ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| #else |
| // get the method ID for the CreateIntArray |
| _createByteBufferCid = env->GetMethodID(javaRenderClass, "CreateByteBuffer", "(II)Ljava/nio/ByteBuffer;"); |
| if (_createByteBufferCid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get CreateByteBuffer ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| |
| // get the method ID for the DrawByteBuffer function |
| _drawByteBufferCid = env->GetMethodID(javaRenderClass, "DrawByteBuffer", "()V"); |
| if (_drawByteBufferCid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get DrawByteBuffer ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| #endif |
| |
| // get the method ID for the SetCoordinates function |
| _setCoordinatesCid = env->GetMethodID(javaRenderClass, "SetCoordinates", "(FFFF)V"); |
| if (_setCoordinatesCid == NULL) |
| { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not get SetCoordinates ID", __FUNCTION__); |
| return -1; /* exception thrown */ |
| } |
| |
| env->CallVoidMethod(_javaRenderObj,_setCoordinatesCid,left,top,right,bottom); |
| |
| // Detach this thread if it was attached |
| if (isAttached) |
| { |
| if (_jvm->DetachCurrentThread() < 0) |
| { |
| WEBRTC_TRACE(kTraceWarning, kTraceVideoRenderer, _id, "%s: Could not detach thread from JVM", __FUNCTION__); |
| } |
| } |
| |
| |
| WEBRTC_TRACE(kTraceDebug, kTraceVideoRenderer, _id, "%s: AndroidSurfaceViewChannel done", __FUNCTION__); |
| return 0; |
| } |
| |
| |
| WebRtc_Word32 AndroidSurfaceViewChannel::RenderFrame(const WebRtc_UWord32 /*streamId*/, VideoFrame& videoFrame) |
| { |
| // WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer,_id, "%s:" ,__FUNCTION__); |
| _renderCritSect.Enter(); |
| _bufferToRender.SwapFrame(videoFrame); |
| _renderCritSect.Leave(); |
| _renderer.ReDraw(); |
| return 0; |
| } |
| |
| |
| /*Implements AndroidStream |
| * Calls the Java object and render the buffer in _bufferToRender |
| */ |
| void AndroidSurfaceViewChannel::DeliverFrame(JNIEnv* jniEnv) { |
| _renderCritSect.Enter(); |
| |
| #ifdef ANDROID_NDK_8_OR_ABOVE |
| if (_bitmapWidth != _bufferToRender.Width() || |
| _bitmapHeight != _bufferToRender.Height()) { |
| // Create the bitmap to write to |
| WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "%s: Creating bitmap %u " |
| "%u", __FUNCTION__, _bufferToRender.Width(), |
| _bufferToRender.Height()); |
| if (_javaBitmapObj) { |
| jniEnv->DeleteGlobalRef(_javaBitmapObj); |
| _javaBitmapObj = NULL; |
| } |
| jobject javaBitmap = jniEnv->CallObjectMethod(_javaRenderObj, |
| _createBitmapCid, |
| videoFrame.Width(), |
| videoFrame.Height()); |
| _javaBitmapObj = jniEnv->NewGlobalRef(javaBitmap); |
| if (!_javaBitmapObj) { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not " |
| "create Java Bitmap object reference", __FUNCTION__); |
| _renderCritSect.Leave(); |
| return; |
| } else { |
| _bitmapWidth=_bufferToRender.Width(); |
| _bitmapHeight=_bufferToRender.Height(); |
| } |
| } |
| void* pixels; |
| if (_javaBitmapObj && |
| AndroidBitmap_lockPixels(jniEnv, _javaBitmapObj, &pixels) >= 0) { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "%s: Locked bitmap", |
| __FUNCTION__); |
| // Convert I420 straight into the Java bitmap. |
| int ret = ConvertI420ToRGB565((unsigned char* )_bufferToRender.Buffer(), |
| (unsigned char* ) pixels, |
| _bitmapWidth, _bitmapHeight); |
| if (ret < 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Color conversion " |
| "failed.", __FUNCTION__); |
| } |
| |
| AndroidBitmap_unlockPixels(jniEnv, _javaBitmapObj); |
| // Draw the Surface. |
| jniEnv->CallVoidMethod(_javaRenderObj,_drawCid); |
| |
| } else { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Could not lock " |
| "bitmap", __FUNCTION__); |
| } |
| _renderCritSect.Leave(); |
| |
| #else |
| if (_bitmapWidth != _bufferToRender.Width() || |
| _bitmapHeight != _bufferToRender.Height()) { |
| WEBRTC_TRACE(kTraceInfo, kTraceVideoRenderer, _id, "%s: New render size %d " |
| "%d",__FUNCTION__, |
| _bufferToRender.Width(), _bufferToRender.Height()); |
| if (_javaByteBufferObj) { |
| jniEnv->DeleteGlobalRef(_javaByteBufferObj); |
| _javaByteBufferObj = NULL; |
| _directBuffer = NULL; |
| } |
| jobject javaByteBufferObj = |
| jniEnv->CallObjectMethod(_javaRenderObj, _createByteBufferCid, |
| _bufferToRender.Width(), |
| _bufferToRender.Height()); |
| _javaByteBufferObj = jniEnv->NewGlobalRef(javaByteBufferObj); |
| if (!_javaByteBufferObj) { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: could not " |
| "create Java ByteBuffer object reference", __FUNCTION__); |
| _renderCritSect.Leave(); |
| return; |
| } else { |
| _directBuffer = static_cast<unsigned char*> |
| (jniEnv->GetDirectBufferAddress(_javaByteBufferObj)); |
| _bitmapWidth = _bufferToRender.Width(); |
| _bitmapHeight = _bufferToRender.Height(); |
| } |
| } |
| |
| if(_javaByteBufferObj && _bitmapWidth && _bitmapHeight) { |
| // Android requires a vertically flipped image compared to std convert. |
| // This is done by giving a negative height input. |
| const int conversionResult = |
| ConvertI420ToRGB565((unsigned char* )_bufferToRender.Buffer(), |
| _directBuffer, _bitmapWidth, -_bitmapHeight); |
| if (conversionResult < 0) { |
| WEBRTC_TRACE(kTraceError, kTraceVideoRenderer, _id, "%s: Color conversion" |
| " failed.", __FUNCTION__); |
| _renderCritSect.Leave(); |
| return; |
| } |
| } |
| _renderCritSect.Leave(); |
| // Draw the Surface |
| jniEnv->CallVoidMethod(_javaRenderObj, _drawByteBufferCid); |
| #endif |
| } |
| |
| } // namespace webrtc |
| |