blob: 253d831ee59ee6ed9a9d6ce628fb3206b486bf17 [file] [log] [blame]
/*
* 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