2013-07-10 00:45:36 +00:00
|
|
|
/*
|
|
|
|
|
* libjingle
|
2015-01-20 21:36:13 +00:00
|
|
|
* Copyright 2013 Google Inc.
|
2013-07-10 00:45:36 +00:00
|
|
|
*
|
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
|
*
|
|
|
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
|
|
|
* and/or other materials provided with the distribution.
|
|
|
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
|
|
|
* derived from this software without specific prior written permission.
|
|
|
|
|
*
|
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
|
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
|
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
|
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
|
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Hints for future visitors:
|
|
|
|
|
// This entire file is an implementation detail of the org.webrtc Java package,
|
|
|
|
|
// the most interesting bits of which are org.webrtc.PeerConnection{,Factory}.
|
|
|
|
|
// The layout of this file is roughly:
|
|
|
|
|
// - various helper C++ functions & classes that wrap Java counterparts and
|
|
|
|
|
// expose a C++ interface that can be passed to the C++ PeerConnection APIs
|
|
|
|
|
// - implementations of methods declared "static" in the Java package (named
|
|
|
|
|
// things like Java_org_webrtc_OMG_Can_This_Name_Be_Any_Longer, prescribed by
|
|
|
|
|
// the JNI spec).
|
|
|
|
|
//
|
|
|
|
|
// Lifecycle notes: objects are owned where they will be called; in other words
|
|
|
|
|
// FooObservers are owned by C++-land, and user-callable objects (e.g.
|
|
|
|
|
// PeerConnection and VideoTrack) are owned by Java-land.
|
|
|
|
|
// When this file allocates C++ RefCountInterfaces it AddRef()s an artificial
|
|
|
|
|
// ref simulating the jlong held in Java-land, and then Release()s the ref in
|
|
|
|
|
// the respective free call. Sometimes this AddRef is implicit in the
|
|
|
|
|
// construction of a scoped_refptr<> which is then .release()d.
|
|
|
|
|
// Any persistent (non-local) references from C++ to Java must be global or weak
|
|
|
|
|
// (in which case they must be checked before use)!
|
|
|
|
|
//
|
|
|
|
|
// Exception notes: pretty much all JNI calls can throw Java exceptions, so each
|
|
|
|
|
// call through a JNIEnv* pointer needs to be followed by an ExceptionCheck()
|
|
|
|
|
// call. In this file this is done in CHECK_EXCEPTION, making for much easier
|
|
|
|
|
// debugging in case of failure (the alternative is to wait for control to
|
|
|
|
|
// return to the Java frame that called code in this file, at which point it's
|
|
|
|
|
// impossible to tell which JNI call broke).
|
|
|
|
|
|
|
|
|
|
#include <jni.h>
|
|
|
|
|
#undef JNIEXPORT
|
|
|
|
|
#define JNIEXPORT __attribute__((visibility("default")))
|
|
|
|
|
|
2013-08-12 23:26:21 +00:00
|
|
|
#include <asm/unistd.h>
|
|
|
|
|
#include <sys/prctl.h>
|
|
|
|
|
#include <sys/syscall.h>
|
2013-12-09 21:34:30 +00:00
|
|
|
#include <unistd.h>
|
2014-08-13 17:26:08 +00:00
|
|
|
#include <limits>
|
|
|
|
|
#include <map>
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
#include "talk/app/webrtc/mediaconstraintsinterface.h"
|
|
|
|
|
#include "talk/app/webrtc/peerconnectioninterface.h"
|
|
|
|
|
#include "talk/app/webrtc/videosourceinterface.h"
|
|
|
|
|
#include "talk/media/base/videocapturer.h"
|
|
|
|
|
#include "talk/media/base/videorenderer.h"
|
|
|
|
|
#include "talk/media/devices/videorendererfactory.h"
|
|
|
|
|
#include "talk/media/webrtc/webrtcvideocapturer.h"
|
2014-07-22 17:44:53 +00:00
|
|
|
#include "talk/media/webrtc/webrtcvideodecoderfactory.h"
|
2014-02-13 03:56:14 +00:00
|
|
|
#include "talk/media/webrtc/webrtcvideoencoderfactory.h"
|
2013-07-30 17:14:35 +00:00
|
|
|
#include "third_party/icu/source/common/unicode/unistr.h"
|
2014-07-22 17:44:53 +00:00
|
|
|
#include "third_party/libyuv/include/libyuv/convert.h"
|
2014-06-10 23:48:29 +00:00
|
|
|
#include "third_party/libyuv/include/libyuv/convert_from.h"
|
|
|
|
|
#include "third_party/libyuv/include/libyuv/video_common.h"
|
2014-08-13 17:26:08 +00:00
|
|
|
#include "webrtc/base/bind.h"
|
2014-09-09 11:45:44 +00:00
|
|
|
#include "webrtc/base/checks.h"
|
2014-08-13 17:26:08 +00:00
|
|
|
#include "webrtc/base/logging.h"
|
|
|
|
|
#include "webrtc/base/messagequeue.h"
|
|
|
|
|
#include "webrtc/base/ssladapter.h"
|
2015-02-09 23:25:58 +00:00
|
|
|
#include "webrtc/base/stringutils.h"
|
2014-09-15 17:52:42 +00:00
|
|
|
#include "webrtc/common_video/interface/texture_video_frame.h"
|
2014-02-13 03:56:14 +00:00
|
|
|
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
|
2015-02-09 23:25:58 +00:00
|
|
|
#include "webrtc/system_wrappers/interface/field_trial_default.h"
|
2013-07-17 14:42:53 +00:00
|
|
|
#include "webrtc/system_wrappers/interface/trace.h"
|
|
|
|
|
#include "webrtc/video_engine/include/vie_base.h"
|
|
|
|
|
#include "webrtc/voice_engine/include/voe_base.h"
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
|
|
|
|
#include <android/log.h>
|
2015-02-11 11:26:56 +00:00
|
|
|
#include "talk/app/webrtc/androidvideocapturer.h"
|
2014-09-17 11:44:51 +00:00
|
|
|
#include "webrtc/modules/video_render/video_render_internal.h"
|
2013-10-01 02:40:43 +00:00
|
|
|
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
|
2014-09-15 17:52:42 +00:00
|
|
|
#include "webrtc/system_wrappers/interface/tick_util.h"
|
2014-07-22 17:44:53 +00:00
|
|
|
using webrtc::CodecSpecificInfo;
|
|
|
|
|
using webrtc::DecodedImageCallback;
|
|
|
|
|
using webrtc::EncodedImage;
|
|
|
|
|
using webrtc::I420VideoFrame;
|
2013-10-01 02:40:43 +00:00
|
|
|
using webrtc::LogcatTraceContext;
|
2014-07-22 17:44:53 +00:00
|
|
|
using webrtc::RTPFragmentationHeader;
|
2014-09-15 17:52:42 +00:00
|
|
|
using webrtc::TextureVideoFrame;
|
|
|
|
|
using webrtc::TickTime;
|
2014-07-22 17:44:53 +00:00
|
|
|
using webrtc::VideoCodec;
|
2013-10-01 02:40:43 +00:00
|
|
|
#endif
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
using icu::UnicodeString;
|
2014-07-29 17:36:52 +00:00
|
|
|
using rtc::Bind;
|
|
|
|
|
using rtc::Thread;
|
|
|
|
|
using rtc::ThreadManager;
|
|
|
|
|
using rtc::scoped_ptr;
|
2013-07-10 00:45:36 +00:00
|
|
|
using webrtc::AudioSourceInterface;
|
|
|
|
|
using webrtc::AudioTrackInterface;
|
|
|
|
|
using webrtc::AudioTrackVector;
|
|
|
|
|
using webrtc::CreateSessionDescriptionObserver;
|
2013-07-12 16:04:50 +00:00
|
|
|
using webrtc::DataBuffer;
|
|
|
|
|
using webrtc::DataChannelInit;
|
|
|
|
|
using webrtc::DataChannelInterface;
|
|
|
|
|
using webrtc::DataChannelObserver;
|
2013-07-10 00:45:36 +00:00
|
|
|
using webrtc::IceCandidateInterface;
|
2014-09-15 17:52:42 +00:00
|
|
|
using webrtc::NativeHandle;
|
2013-07-10 00:45:36 +00:00
|
|
|
using webrtc::MediaConstraintsInterface;
|
|
|
|
|
using webrtc::MediaSourceInterface;
|
|
|
|
|
using webrtc::MediaStreamInterface;
|
|
|
|
|
using webrtc::MediaStreamTrackInterface;
|
|
|
|
|
using webrtc::PeerConnectionFactoryInterface;
|
|
|
|
|
using webrtc::PeerConnectionInterface;
|
|
|
|
|
using webrtc::PeerConnectionObserver;
|
|
|
|
|
using webrtc::SessionDescriptionInterface;
|
|
|
|
|
using webrtc::SetSessionDescriptionObserver;
|
|
|
|
|
using webrtc::StatsObserver;
|
|
|
|
|
using webrtc::StatsReport;
|
2014-12-15 13:22:54 +00:00
|
|
|
using webrtc::StatsReports;
|
2013-07-10 00:45:36 +00:00
|
|
|
using webrtc::VideoRendererInterface;
|
|
|
|
|
using webrtc::VideoSourceInterface;
|
|
|
|
|
using webrtc::VideoTrackInterface;
|
|
|
|
|
using webrtc::VideoTrackVector;
|
2014-02-13 03:56:14 +00:00
|
|
|
using webrtc::kVideoCodecVP8;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2014-09-09 11:45:44 +00:00
|
|
|
// Abort the process if |jni| has a Java exception pending.
|
|
|
|
|
// This macros uses the comma operator to execute ExceptionDescribe
|
|
|
|
|
// and ExceptionClear ignoring their return values and sending ""
|
|
|
|
|
// to the error stream.
|
|
|
|
|
#define CHECK_EXCEPTION(jni) \
|
|
|
|
|
CHECK(!jni->ExceptionCheck()) \
|
|
|
|
|
<< (jni->ExceptionDescribe(), jni->ExceptionClear(), "")
|
|
|
|
|
|
|
|
|
|
// Helper that calls ptr->Release() and aborts the process with a useful
|
|
|
|
|
// message if that didn't actually delete *ptr because of extra refcounts.
|
|
|
|
|
#define CHECK_RELEASE(ptr) \
|
|
|
|
|
CHECK_EQ(0, (ptr)->Release()) << "Unexpected refcount."
|
2013-08-12 23:26:21 +00:00
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
static JavaVM* g_jvm = NULL; // Set in JNI_OnLoad().
|
|
|
|
|
|
|
|
|
|
static pthread_once_t g_jni_ptr_once = PTHREAD_ONCE_INIT;
|
2014-02-18 16:57:36 +00:00
|
|
|
// Key for per-thread JNIEnv* data. Non-NULL in threads attached to |g_jvm| by
|
|
|
|
|
// AttachCurrentThreadIfNeeded(), NULL in unattached threads and threads that
|
|
|
|
|
// were attached by the JVM because of a Java->native call.
|
|
|
|
|
static pthread_key_t g_jni_ptr;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-09 23:25:58 +00:00
|
|
|
// Field trials initialization string
|
|
|
|
|
static char *field_trials_init_string = NULL;
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
|
|
|
|
// Set in PeerConnectionFactory_initializeAndroidGlobals().
|
|
|
|
|
static bool factory_static_initialized = false;
|
2014-12-01 20:02:13 +00:00
|
|
|
static bool vp8_hw_acceleration_enabled = true;
|
2014-09-15 17:52:42 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
2013-08-12 23:26:21 +00:00
|
|
|
// Return thread ID as a string.
|
|
|
|
|
static std::string GetThreadId() {
|
|
|
|
|
char buf[21]; // Big enough to hold a kuint64max plus terminating NULL.
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_LT(snprintf(buf, sizeof(buf), "%llu", syscall(__NR_gettid)),
|
|
|
|
|
sizeof(buf))
|
|
|
|
|
<< "Thread id is bigger than uint64??";
|
2013-08-12 23:26:21 +00:00
|
|
|
return std::string(buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the current thread's name.
|
|
|
|
|
static std::string GetThreadName() {
|
|
|
|
|
char name[17];
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EQ(0, prctl(PR_GET_NAME, name)) << "prctl(PR_GET_NAME) failed";
|
2013-08-12 23:26:21 +00:00
|
|
|
name[16] = '\0';
|
|
|
|
|
return std::string(name);
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-18 16:57:36 +00:00
|
|
|
// Return a |JNIEnv*| usable on this thread or NULL if this thread is detached.
|
|
|
|
|
static JNIEnv* GetEnv() {
|
|
|
|
|
void* env = NULL;
|
|
|
|
|
jint status = g_jvm->GetEnv(&env, JNI_VERSION_1_6);
|
|
|
|
|
CHECK(((env != NULL) && (status == JNI_OK)) ||
|
2014-09-09 11:45:44 +00:00
|
|
|
((env == NULL) && (status == JNI_EDETACHED)))
|
|
|
|
|
<< "Unexpected GetEnv return: " << status << ":" << env;
|
2014-02-18 16:57:36 +00:00
|
|
|
return reinterpret_cast<JNIEnv*>(env);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ThreadDestructor(void* prev_jni_ptr) {
|
|
|
|
|
// This function only runs on threads where |g_jni_ptr| is non-NULL, meaning
|
|
|
|
|
// we were responsible for originally attaching the thread, so are responsible
|
|
|
|
|
// for detaching it now. However, because some JVM implementations (notably
|
|
|
|
|
// Oracle's http://goo.gl/eHApYT) also use the pthread_key_create mechanism,
|
|
|
|
|
// the JVMs accounting info for this thread may already be wiped out by the
|
|
|
|
|
// time this is called. Thus it may appear we are already detached even though
|
|
|
|
|
// it was our responsibility to detach! Oh well.
|
|
|
|
|
if (!GetEnv())
|
|
|
|
|
return;
|
|
|
|
|
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(GetEnv() == prev_jni_ptr)
|
|
|
|
|
<< "Detaching from another thread: " << prev_jni_ptr << ":" << GetEnv();
|
2013-07-10 00:45:36 +00:00
|
|
|
jint status = g_jvm->DetachCurrentThread();
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(status == JNI_OK) << "Failed to detach thread: " << status;
|
|
|
|
|
CHECK(!GetEnv()) << "Detaching was a successful no-op???";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void CreateJNIPtrKey() {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor))
|
|
|
|
|
<< "pthread_key_create";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2014-02-18 16:57:36 +00:00
|
|
|
// Return a |JNIEnv*| usable on this thread. Attaches to |g_jvm| if necessary.
|
2013-07-10 00:45:36 +00:00
|
|
|
static JNIEnv* AttachCurrentThreadIfNeeded() {
|
2014-02-18 16:57:36 +00:00
|
|
|
JNIEnv* jni = GetEnv();
|
|
|
|
|
if (jni)
|
|
|
|
|
return jni;
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!pthread_getspecific(g_jni_ptr))
|
|
|
|
|
<< "TLS has a JNIEnv* but not attached?";
|
2014-02-18 16:57:36 +00:00
|
|
|
|
|
|
|
|
char* name = strdup((GetThreadName() + " - " + GetThreadId()).c_str());
|
|
|
|
|
JavaVMAttachArgs args;
|
|
|
|
|
args.version = JNI_VERSION_1_6;
|
|
|
|
|
args.name = name;
|
|
|
|
|
args.group = NULL;
|
|
|
|
|
// Deal with difference in signatures between Oracle's jni.h and Android's.
|
2013-07-10 00:45:36 +00:00
|
|
|
#ifdef _JAVASOFT_JNI_H_ // Oracle's jni.h violates the JNI spec!
|
2014-02-18 16:57:36 +00:00
|
|
|
void* env = NULL;
|
2013-07-10 00:45:36 +00:00
|
|
|
#else
|
2014-02-18 16:57:36 +00:00
|
|
|
JNIEnv* env = NULL;
|
2013-07-10 00:45:36 +00:00
|
|
|
#endif
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!g_jvm->AttachCurrentThread(&env, &args)) << "Failed to attach thread";
|
2014-02-18 16:57:36 +00:00
|
|
|
free(name);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(env) << "AttachCurrentThread handed back NULL!";
|
2014-02-18 16:57:36 +00:00
|
|
|
jni = reinterpret_cast<JNIEnv*>(env);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!pthread_setspecific(g_jni_ptr, jni)) << "pthread_setspecific";
|
2013-07-10 00:45:36 +00:00
|
|
|
return jni;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-11 21:07:18 +00:00
|
|
|
// Return a |jlong| that will correctly convert back to |ptr|. This is needed
|
|
|
|
|
// because the alternative (of silently passing a 32-bit pointer to a vararg
|
|
|
|
|
// function expecting a 64-bit param) picks up garbage in the high 32 bits.
|
2013-09-03 18:58:12 +00:00
|
|
|
static jlong jlongFromPointer(void* ptr) {
|
2015-01-14 10:51:54 +00:00
|
|
|
static_assert(sizeof(intptr_t) <= sizeof(jlong),
|
|
|
|
|
"Time to rethink the use of jlongs");
|
2013-12-11 21:07:18 +00:00
|
|
|
// Going through intptr_t to be obvious about the definedness of the
|
|
|
|
|
// conversion from pointer to integral type. intptr_t to jlong is a standard
|
2015-01-14 10:51:54 +00:00
|
|
|
// widening by the static_assert above.
|
2013-12-11 21:07:18 +00:00
|
|
|
jlong ret = reinterpret_cast<intptr_t>(ptr);
|
|
|
|
|
assert(reinterpret_cast<void*>(ret) == ptr);
|
|
|
|
|
return ret;
|
2013-09-03 18:58:12 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
// Android's FindClass() is trickier than usual because the app-specific
|
|
|
|
|
// ClassLoader is not consulted when there is no app-specific frame on the
|
|
|
|
|
// stack. Consequently, we only look up classes once in JNI_OnLoad.
|
|
|
|
|
// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass
|
|
|
|
|
class ClassReferenceHolder {
|
|
|
|
|
public:
|
|
|
|
|
explicit ClassReferenceHolder(JNIEnv* jni) {
|
|
|
|
|
LoadClass(jni, "java/nio/ByteBuffer");
|
|
|
|
|
LoadClass(jni, "org/webrtc/AudioTrack");
|
2013-07-12 16:04:50 +00:00
|
|
|
LoadClass(jni, "org/webrtc/DataChannel");
|
|
|
|
|
LoadClass(jni, "org/webrtc/DataChannel$Buffer");
|
|
|
|
|
LoadClass(jni, "org/webrtc/DataChannel$Init");
|
|
|
|
|
LoadClass(jni, "org/webrtc/DataChannel$State");
|
2013-07-10 00:45:36 +00:00
|
|
|
LoadClass(jni, "org/webrtc/IceCandidate");
|
2014-09-15 17:52:42 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
|
|
|
|
LoadClass(jni, "android/graphics/SurfaceTexture");
|
2015-02-11 11:26:56 +00:00
|
|
|
LoadClass(jni, "org/webrtc/VideoCapturerAndroid");
|
|
|
|
|
LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeFrameObserver");
|
2014-02-13 03:56:14 +00:00
|
|
|
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder");
|
|
|
|
|
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
|
2014-07-22 17:44:53 +00:00
|
|
|
LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder");
|
2014-09-15 17:52:42 +00:00
|
|
|
LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo");
|
2014-10-20 19:08:05 +00:00
|
|
|
jclass j_decoder_class = GetClass("org/webrtc/MediaCodecVideoDecoder");
|
|
|
|
|
jmethodID j_is_egl14_supported_method = jni->GetStaticMethodID(
|
|
|
|
|
j_decoder_class, "isEGL14Supported", "()Z");
|
|
|
|
|
bool is_egl14_supported = jni->CallStaticBooleanMethod(
|
|
|
|
|
j_decoder_class, j_is_egl14_supported_method);
|
|
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
if (is_egl14_supported) {
|
|
|
|
|
LoadClass(jni, "android/opengl/EGLContext");
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
#endif
|
2013-07-10 00:45:36 +00:00
|
|
|
LoadClass(jni, "org/webrtc/MediaSource$State");
|
|
|
|
|
LoadClass(jni, "org/webrtc/MediaStream");
|
|
|
|
|
LoadClass(jni, "org/webrtc/MediaStreamTrack$State");
|
|
|
|
|
LoadClass(jni, "org/webrtc/PeerConnection$IceConnectionState");
|
|
|
|
|
LoadClass(jni, "org/webrtc/PeerConnection$IceGatheringState");
|
2014-02-13 03:56:14 +00:00
|
|
|
LoadClass(jni, "org/webrtc/PeerConnection$SignalingState");
|
2013-07-10 00:45:36 +00:00
|
|
|
LoadClass(jni, "org/webrtc/SessionDescription");
|
|
|
|
|
LoadClass(jni, "org/webrtc/SessionDescription$Type");
|
|
|
|
|
LoadClass(jni, "org/webrtc/StatsReport");
|
|
|
|
|
LoadClass(jni, "org/webrtc/StatsReport$Value");
|
|
|
|
|
LoadClass(jni, "org/webrtc/VideoRenderer$I420Frame");
|
2015-02-11 11:26:56 +00:00
|
|
|
LoadClass(jni, "org/webrtc/VideoCapturer");
|
2013-07-10 00:45:36 +00:00
|
|
|
LoadClass(jni, "org/webrtc/VideoTrack");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~ClassReferenceHolder() {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(classes_.empty()) << "Must call FreeReferences() before dtor!";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeReferences(JNIEnv* jni) {
|
|
|
|
|
for (std::map<std::string, jclass>::const_iterator it = classes_.begin();
|
|
|
|
|
it != classes_.end(); ++it) {
|
|
|
|
|
jni->DeleteGlobalRef(it->second);
|
|
|
|
|
}
|
|
|
|
|
classes_.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jclass GetClass(const std::string& name) {
|
|
|
|
|
std::map<std::string, jclass>::iterator it = classes_.find(name);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(it != classes_.end()) << "Unexpected GetClass() call for: " << name;
|
2013-07-10 00:45:36 +00:00
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void LoadClass(JNIEnv* jni, const std::string& name) {
|
|
|
|
|
jclass localRef = jni->FindClass(name.c_str());
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during FindClass: " << name;
|
|
|
|
|
CHECK(localRef) << name;
|
2013-07-10 00:45:36 +00:00
|
|
|
jclass globalRef = reinterpret_cast<jclass>(jni->NewGlobalRef(localRef));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewGlobalRef: " << name;
|
|
|
|
|
CHECK(globalRef) << name;
|
2013-07-10 00:45:36 +00:00
|
|
|
bool inserted = classes_.insert(std::make_pair(name, globalRef)).second;
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(inserted) << "Duplicate class name: " << name;
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::map<std::string, jclass> classes_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Allocated in JNI_OnLoad(), freed in JNI_OnUnLoad().
|
|
|
|
|
static ClassReferenceHolder* g_class_reference_holder = NULL;
|
|
|
|
|
|
|
|
|
|
// JNIEnv-helper methods that CHECK success: no Java exception thrown and found
|
|
|
|
|
// object/class/method/field is non-null.
|
|
|
|
|
jmethodID GetMethodID(
|
|
|
|
|
JNIEnv* jni, jclass c, const std::string& name, const char* signature) {
|
|
|
|
|
jmethodID m = jni->GetMethodID(c, name.c_str(), signature);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetMethodID: " << name << ", "
|
|
|
|
|
<< signature;
|
|
|
|
|
CHECK(m) << name << ", " << signature;
|
2013-07-10 00:45:36 +00:00
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jmethodID GetStaticMethodID(
|
|
|
|
|
JNIEnv* jni, jclass c, const char* name, const char* signature) {
|
|
|
|
|
jmethodID m = jni->GetStaticMethodID(c, name, signature);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetStaticMethodID: " << name << ", "
|
|
|
|
|
<< signature;
|
|
|
|
|
CHECK(m) << name << ", " << signature;
|
2013-07-10 00:45:36 +00:00
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jfieldID GetFieldID(
|
|
|
|
|
JNIEnv* jni, jclass c, const char* name, const char* signature) {
|
|
|
|
|
jfieldID f = jni->GetFieldID(c, name, signature);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetFieldID";
|
|
|
|
|
CHECK(f) << name << ", " << signature;
|
2013-07-10 00:45:36 +00:00
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
// Returns a global reference guaranteed to be valid for the lifetime of the
|
|
|
|
|
// process.
|
2013-07-10 00:45:36 +00:00
|
|
|
jclass FindClass(JNIEnv* jni, const char* name) {
|
|
|
|
|
return g_class_reference_holder->GetClass(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jclass GetObjectClass(JNIEnv* jni, jobject object) {
|
|
|
|
|
jclass c = jni->GetObjectClass(object);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetObjectClass";
|
2014-09-15 17:52:42 +00:00
|
|
|
CHECK(c) << "GetObjectClass returned NULL";
|
2013-07-10 00:45:36 +00:00
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jobject GetObjectField(JNIEnv* jni, jobject object, jfieldID id) {
|
|
|
|
|
jobject o = jni->GetObjectField(object, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetObjectField";
|
2014-09-15 17:52:42 +00:00
|
|
|
CHECK(o) << "GetObjectField returned NULL";
|
2013-07-10 00:45:36 +00:00
|
|
|
return o;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
jstring GetStringField(JNIEnv* jni, jobject object, jfieldID id) {
|
|
|
|
|
return static_cast<jstring>(GetObjectField(jni, object, id));
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
jlong GetLongField(JNIEnv* jni, jobject object, jfieldID id) {
|
|
|
|
|
jlong l = jni->GetLongField(object, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetLongField";
|
2013-07-10 00:45:36 +00:00
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
jint GetIntField(JNIEnv* jni, jobject object, jfieldID id) {
|
|
|
|
|
jint i = jni->GetIntField(object, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetIntField";
|
2013-07-12 16:04:50 +00:00
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetBooleanField(JNIEnv* jni, jobject object, jfieldID id) {
|
|
|
|
|
jboolean b = jni->GetBooleanField(object, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetBooleanField";
|
2013-07-12 16:04:50 +00:00
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
jobject NewGlobalRef(JNIEnv* jni, jobject o) {
|
|
|
|
|
jobject ret = jni->NewGlobalRef(o);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewGlobalRef";
|
|
|
|
|
CHECK(ret);
|
2013-07-10 00:45:36 +00:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DeleteGlobalRef(JNIEnv* jni, jobject o) {
|
|
|
|
|
jni->DeleteGlobalRef(o);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during DeleteGlobalRef";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
// Convenience macro defining JNI-accessible methods in the org.webrtc package.
|
|
|
|
|
// Eliminates unnecessary boilerplate and line-wraps, reducing visual clutter.
|
|
|
|
|
#define JOW(rettype, name) extern "C" rettype JNIEXPORT JNICALL \
|
|
|
|
|
Java_org_webrtc_##name
|
|
|
|
|
|
|
|
|
|
extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
|
|
|
|
CHECK(!g_jvm) << "JNI_OnLoad called more than once!";
|
|
|
|
|
g_jvm = jvm;
|
|
|
|
|
CHECK(g_jvm) << "JNI_OnLoad handed NULL?";
|
|
|
|
|
|
|
|
|
|
CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey)) << "pthread_once";
|
|
|
|
|
|
|
|
|
|
CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
|
|
|
|
|
|
|
|
|
|
JNIEnv* jni;
|
|
|
|
|
if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK)
|
|
|
|
|
return -1;
|
|
|
|
|
g_class_reference_holder = new ClassReferenceHolder(jni);
|
|
|
|
|
|
|
|
|
|
return JNI_VERSION_1_6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM *jvm, void *reserved) {
|
|
|
|
|
g_class_reference_holder->FreeReferences(AttachCurrentThreadIfNeeded());
|
|
|
|
|
delete g_class_reference_holder;
|
|
|
|
|
g_class_reference_holder = NULL;
|
|
|
|
|
CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
|
|
|
|
|
g_jvm = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
// Given a jweak reference, allocate a (strong) local reference scoped to the
|
|
|
|
|
// lifetime of this object if the weak reference is still valid, or NULL
|
|
|
|
|
// otherwise.
|
|
|
|
|
class WeakRef {
|
|
|
|
|
public:
|
|
|
|
|
WeakRef(JNIEnv* jni, jweak ref)
|
|
|
|
|
: jni_(jni), obj_(jni_->NewLocalRef(ref)) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewLocalRef";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
~WeakRef() {
|
|
|
|
|
if (obj_) {
|
|
|
|
|
jni_->DeleteLocalRef(obj_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni_) << "error during DeleteLocalRef";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
jobject obj() { return obj_; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
JNIEnv* const jni_;
|
|
|
|
|
jobject const obj_;
|
|
|
|
|
};
|
|
|
|
|
|
2014-01-09 00:31:17 +00:00
|
|
|
// Scope Java local references to the lifetime of this object. Use in all C++
|
|
|
|
|
// callbacks (i.e. entry points that don't originate in a Java callstack
|
|
|
|
|
// through a "native" method call).
|
|
|
|
|
class ScopedLocalRefFrame {
|
2013-07-10 00:45:36 +00:00
|
|
|
public:
|
2014-01-09 00:31:17 +00:00
|
|
|
explicit ScopedLocalRefFrame(JNIEnv* jni) : jni_(jni) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!jni_->PushLocalFrame(0)) << "Failed to PushLocalFrame";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
2014-01-09 00:31:17 +00:00
|
|
|
~ScopedLocalRefFrame() {
|
|
|
|
|
jni_->PopLocalFrame(NULL);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
2014-01-09 00:31:17 +00:00
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
private:
|
|
|
|
|
JNIEnv* jni_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Scoped holder for global Java refs.
|
2013-07-12 16:04:50 +00:00
|
|
|
template<class T> // T is jclass, jobject, jintArray, etc.
|
2013-07-10 00:45:36 +00:00
|
|
|
class ScopedGlobalRef {
|
|
|
|
|
public:
|
2014-02-13 03:56:14 +00:00
|
|
|
ScopedGlobalRef(JNIEnv* jni, T obj)
|
2013-07-12 16:04:50 +00:00
|
|
|
: obj_(static_cast<T>(jni->NewGlobalRef(obj))) {}
|
2013-07-10 00:45:36 +00:00
|
|
|
~ScopedGlobalRef() {
|
|
|
|
|
DeleteGlobalRef(AttachCurrentThreadIfNeeded(), obj_);
|
|
|
|
|
}
|
2013-07-12 16:04:50 +00:00
|
|
|
T operator*() const {
|
2013-07-10 00:45:36 +00:00
|
|
|
return obj_;
|
|
|
|
|
}
|
|
|
|
|
private:
|
2013-07-12 16:04:50 +00:00
|
|
|
T obj_;
|
2013-07-10 00:45:36 +00:00
|
|
|
};
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
// Java references to "null" can only be distinguished as such in C++ by
|
|
|
|
|
// creating a local reference, so this helper wraps that logic.
|
|
|
|
|
static bool IsNull(JNIEnv* jni, jobject obj) {
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
return jni->NewLocalRef(obj) == NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
// Return the (singleton) Java Enum object corresponding to |index|;
|
|
|
|
|
// |state_class_fragment| is something like "MediaSource$State".
|
|
|
|
|
jobject JavaEnumFromIndex(
|
|
|
|
|
JNIEnv* jni, const std::string& state_class_fragment, int index) {
|
|
|
|
|
std::string state_class_name = "org/webrtc/" + state_class_fragment;
|
|
|
|
|
jclass state_class = FindClass(jni, state_class_name.c_str());
|
|
|
|
|
jmethodID state_values_id = GetStaticMethodID(
|
|
|
|
|
jni, state_class, "values", ("()[L" + state_class_name + ";").c_str());
|
2014-01-09 00:31:17 +00:00
|
|
|
jobjectArray state_values = static_cast<jobjectArray>(
|
|
|
|
|
jni->CallStaticObjectMethod(state_class, state_values_id));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallStaticObjectMethod";
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject ret = jni->GetObjectArrayElement(state_values, index);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during GetObjectArrayElement";
|
2013-07-10 00:45:36 +00:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Given a UTF-8 encoded |native| string return a new (UTF-16) jstring.
|
|
|
|
|
static jstring JavaStringFromStdString(JNIEnv* jni, const std::string& native) {
|
|
|
|
|
UnicodeString ustr(UnicodeString::fromUTF8(native));
|
|
|
|
|
jstring jstr = jni->NewString(ustr.getBuffer(), ustr.length());
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewString";
|
2013-07-10 00:45:36 +00:00
|
|
|
return jstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Given a (UTF-16) jstring return a new UTF-8 native string.
|
|
|
|
|
static std::string JavaToStdString(JNIEnv* jni, const jstring& j_string) {
|
|
|
|
|
const jchar* jchars = jni->GetStringChars(j_string, NULL);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "Error during GetStringChars";
|
2013-07-10 00:45:36 +00:00
|
|
|
UnicodeString ustr(jchars, jni->GetStringLength(j_string));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "Error during GetStringLength";
|
2013-07-10 00:45:36 +00:00
|
|
|
jni->ReleaseStringChars(j_string, jchars);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "Error during ReleaseStringChars";
|
2013-07-10 00:45:36 +00:00
|
|
|
std::string ret;
|
|
|
|
|
return ustr.toUTF8String(ret);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
static DataChannelInit JavaDataChannelInitToNative(
|
|
|
|
|
JNIEnv* jni, jobject j_init) {
|
|
|
|
|
DataChannelInit init;
|
|
|
|
|
|
|
|
|
|
jclass j_init_class = FindClass(jni, "org/webrtc/DataChannel$Init");
|
|
|
|
|
jfieldID ordered_id = GetFieldID(jni, j_init_class, "ordered", "Z");
|
|
|
|
|
jfieldID max_retransmit_time_id =
|
|
|
|
|
GetFieldID(jni, j_init_class, "maxRetransmitTimeMs", "I");
|
|
|
|
|
jfieldID max_retransmits_id =
|
|
|
|
|
GetFieldID(jni, j_init_class, "maxRetransmits", "I");
|
|
|
|
|
jfieldID protocol_id =
|
|
|
|
|
GetFieldID(jni, j_init_class, "protocol", "Ljava/lang/String;");
|
|
|
|
|
jfieldID negotiated_id = GetFieldID(jni, j_init_class, "negotiated", "Z");
|
|
|
|
|
jfieldID id_id = GetFieldID(jni, j_init_class, "id", "I");
|
|
|
|
|
|
|
|
|
|
init.ordered = GetBooleanField(jni, j_init, ordered_id);
|
|
|
|
|
init.maxRetransmitTime = GetIntField(jni, j_init, max_retransmit_time_id);
|
|
|
|
|
init.maxRetransmits = GetIntField(jni, j_init, max_retransmits_id);
|
|
|
|
|
init.protocol = JavaToStdString(
|
|
|
|
|
jni, GetStringField(jni, j_init, protocol_id));
|
|
|
|
|
init.negotiated = GetBooleanField(jni, j_init, negotiated_id);
|
|
|
|
|
init.id = GetIntField(jni, j_init, id_id);
|
|
|
|
|
|
|
|
|
|
return init;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
class ConstraintsWrapper;
|
|
|
|
|
|
|
|
|
|
// Adapter between the C++ PeerConnectionObserver interface and the Java
|
|
|
|
|
// PeerConnection.Observer interface. Wraps an instance of the Java interface
|
|
|
|
|
// and dispatches C++ callbacks to Java.
|
|
|
|
|
class PCOJava : public PeerConnectionObserver {
|
|
|
|
|
public:
|
|
|
|
|
PCOJava(JNIEnv* jni, jobject j_observer)
|
|
|
|
|
: j_observer_global_(jni, j_observer),
|
2013-07-12 16:04:50 +00:00
|
|
|
j_observer_class_(jni, GetObjectClass(jni, *j_observer_global_)),
|
|
|
|
|
j_media_stream_class_(jni, FindClass(jni, "org/webrtc/MediaStream")),
|
|
|
|
|
j_media_stream_ctor_(GetMethodID(
|
|
|
|
|
jni, *j_media_stream_class_, "<init>", "(J)V")),
|
|
|
|
|
j_audio_track_class_(jni, FindClass(jni, "org/webrtc/AudioTrack")),
|
2013-07-10 00:45:36 +00:00
|
|
|
j_audio_track_ctor_(GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni, *j_audio_track_class_, "<init>", "(J)V")),
|
|
|
|
|
j_video_track_class_(jni, FindClass(jni, "org/webrtc/VideoTrack")),
|
|
|
|
|
j_video_track_ctor_(GetMethodID(
|
|
|
|
|
jni, *j_video_track_class_, "<init>", "(J)V")),
|
|
|
|
|
j_data_channel_class_(jni, FindClass(jni, "org/webrtc/DataChannel")),
|
|
|
|
|
j_data_channel_ctor_(GetMethodID(
|
|
|
|
|
jni, *j_data_channel_class_, "<init>", "(J)V")) {
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual ~PCOJava() {}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
std::string sdp;
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(candidate->ToString(&sdp)) << "got so far: " << sdp;
|
2013-07-10 00:45:36 +00:00
|
|
|
jclass candidate_class = FindClass(jni(), "org/webrtc/IceCandidate");
|
|
|
|
|
jmethodID ctor = GetMethodID(jni(), candidate_class,
|
|
|
|
|
"<init>", "(Ljava/lang/String;ILjava/lang/String;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring j_mid = JavaStringFromStdString(jni(), candidate->sdp_mid());
|
|
|
|
|
jstring j_sdp = JavaStringFromStdString(jni(), sdp);
|
|
|
|
|
jobject j_candidate = jni()->NewObject(
|
|
|
|
|
candidate_class, ctor, j_mid, candidate->sdp_mline_index(), j_sdp);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_,
|
2013-07-10 00:45:36 +00:00
|
|
|
"onIceCandidate", "(Lorg/webrtc/IceCandidate;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_candidate);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void OnSignalingChange(
|
2013-07-12 16:04:50 +00:00
|
|
|
PeerConnectionInterface::SignalingState new_state) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID m = GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni(), *j_observer_class_, "onSignalingChange",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Lorg/webrtc/PeerConnection$SignalingState;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject new_state_enum =
|
|
|
|
|
JavaEnumFromIndex(jni(), "PeerConnection$SignalingState", new_state);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void OnIceConnectionChange(
|
2013-07-12 16:04:50 +00:00
|
|
|
PeerConnectionInterface::IceConnectionState new_state) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID m = GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni(), *j_observer_class_, "onIceConnectionChange",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Lorg/webrtc/PeerConnection$IceConnectionState;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject new_state_enum = JavaEnumFromIndex(
|
|
|
|
|
jni(), "PeerConnection$IceConnectionState", new_state);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void OnIceGatheringChange(
|
2013-07-12 16:04:50 +00:00
|
|
|
PeerConnectionInterface::IceGatheringState new_state) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID m = GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni(), *j_observer_class_, "onIceGatheringChange",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Lorg/webrtc/PeerConnection$IceGatheringState;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject new_state_enum = JavaEnumFromIndex(
|
|
|
|
|
jni(), "PeerConnection$IceGatheringState", new_state);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, new_state_enum);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnAddStream(MediaStreamInterface* stream) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
|
|
|
|
jobject j_stream = jni()->NewObject(
|
|
|
|
|
*j_media_stream_class_, j_media_stream_ctor_, (jlong)stream);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
AudioTrackVector audio_tracks = stream->GetAudioTracks();
|
|
|
|
|
for (size_t i = 0; i < audio_tracks.size(); ++i) {
|
|
|
|
|
AudioTrackInterface* track = audio_tracks[i];
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring id = JavaStringFromStdString(jni(), track->id());
|
|
|
|
|
jobject j_track = jni()->NewObject(
|
|
|
|
|
*j_audio_track_class_, j_audio_track_ctor_, (jlong)track, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-08-12 23:26:21 +00:00
|
|
|
jfieldID audio_tracks_id = GetFieldID(jni(),
|
|
|
|
|
*j_media_stream_class_,
|
|
|
|
|
"audioTracks",
|
|
|
|
|
"Ljava/util/LinkedList;");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject audio_tracks = GetObjectField(jni(), j_stream, audio_tracks_id);
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID add = GetMethodID(jni(),
|
2014-01-09 00:31:17 +00:00
|
|
|
GetObjectClass(jni(), audio_tracks),
|
|
|
|
|
"add",
|
|
|
|
|
"(Ljava/lang/Object;)Z");
|
|
|
|
|
jboolean added = jni()->CallBooleanMethod(audio_tracks, add, j_track);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
|
|
|
|
|
CHECK(added);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VideoTrackVector video_tracks = stream->GetVideoTracks();
|
|
|
|
|
for (size_t i = 0; i < video_tracks.size(); ++i) {
|
|
|
|
|
VideoTrackInterface* track = video_tracks[i];
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring id = JavaStringFromStdString(jni(), track->id());
|
|
|
|
|
jobject j_track = jni()->NewObject(
|
|
|
|
|
*j_video_track_class_, j_video_track_ctor_, (jlong)track, id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-08-12 23:26:21 +00:00
|
|
|
jfieldID video_tracks_id = GetFieldID(jni(),
|
|
|
|
|
*j_media_stream_class_,
|
|
|
|
|
"videoTracks",
|
|
|
|
|
"Ljava/util/LinkedList;");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject video_tracks = GetObjectField(jni(), j_stream, video_tracks_id);
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID add = GetMethodID(jni(),
|
2014-01-09 00:31:17 +00:00
|
|
|
GetObjectClass(jni(), video_tracks),
|
|
|
|
|
"add",
|
|
|
|
|
"(Ljava/lang/Object;)Z");
|
|
|
|
|
jboolean added = jni()->CallBooleanMethod(video_tracks, add, j_track);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
|
|
|
|
|
CHECK(added);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
2014-01-09 00:31:17 +00:00
|
|
|
streams_[stream] = jni()->NewWeakGlobalRef(j_stream);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewWeakGlobalRef";
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onAddStream",
|
|
|
|
|
"(Lorg/webrtc/MediaStream;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_stream);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnRemoveStream(MediaStreamInterface* stream) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
NativeToJavaStreamsMap::iterator it = streams_.find(stream);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(it != streams_.end()) << "unexpected stream: " << std::hex << stream;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
WeakRef s(jni(), it->second);
|
|
|
|
|
streams_.erase(it);
|
|
|
|
|
if (!s.obj())
|
|
|
|
|
return;
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onRemoveStream",
|
|
|
|
|
"(Lorg/webrtc/MediaStream;)V");
|
2013-07-10 00:45:36 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, s.obj());
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnDataChannel(DataChannelInterface* channel) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
|
|
|
|
jobject j_channel = jni()->NewObject(
|
|
|
|
|
*j_data_channel_class_, j_data_channel_ctor_, (jlong)channel);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-07-12 16:04:50 +00:00
|
|
|
|
|
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onDataChannel",
|
|
|
|
|
"(Lorg/webrtc/DataChannel;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_channel);
|
2013-08-12 23:26:21 +00:00
|
|
|
|
|
|
|
|
// Channel is now owned by Java object, and will be freed from
|
|
|
|
|
// DataChannel.dispose(). Important that this be done _after_ the
|
|
|
|
|
// CallVoidMethod above as Java code might call back into native code and be
|
|
|
|
|
// surprised to see a refcount of 2.
|
|
|
|
|
int bumped_count = channel->AddRef();
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(bumped_count == 2) << "Unexpected refcount OnDataChannel";
|
2013-08-12 23:26:21 +00:00
|
|
|
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
2014-01-13 22:04:12 +00:00
|
|
|
virtual void OnRenegotiationNeeded() OVERRIDE {
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
|
|
|
|
jmethodID m =
|
|
|
|
|
GetMethodID(jni(), *j_observer_class_, "onRenegotiationNeeded", "()V");
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2014-01-13 22:04:12 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
void SetConstraints(ConstraintsWrapper* constraints) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!constraints_.get()) << "constraints already set!";
|
2013-07-10 00:45:36 +00:00
|
|
|
constraints_.reset(constraints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ConstraintsWrapper* constraints() { return constraints_.get(); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
JNIEnv* jni() {
|
|
|
|
|
return AttachCurrentThreadIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jobject> j_observer_global_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_observer_class_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_media_stream_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
const jmethodID j_media_stream_ctor_;
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jclass> j_audio_track_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
const jmethodID j_audio_track_ctor_;
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jclass> j_video_track_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
const jmethodID j_video_track_ctor_;
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jclass> j_data_channel_class_;
|
|
|
|
|
const jmethodID j_data_channel_ctor_;
|
2013-07-10 00:45:36 +00:00
|
|
|
typedef std::map<void*, jweak> NativeToJavaStreamsMap;
|
|
|
|
|
NativeToJavaStreamsMap streams_; // C++ -> Java streams.
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<ConstraintsWrapper> constraints_;
|
2013-07-10 00:45:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Wrapper for a Java MediaConstraints object. Copies all needed data so when
|
|
|
|
|
// the constructor returns the Java object is no longer needed.
|
|
|
|
|
class ConstraintsWrapper : public MediaConstraintsInterface {
|
|
|
|
|
public:
|
|
|
|
|
ConstraintsWrapper(JNIEnv* jni, jobject j_constraints) {
|
|
|
|
|
PopulateConstraintsFromJavaPairList(
|
|
|
|
|
jni, j_constraints, "mandatory", &mandatory_);
|
|
|
|
|
PopulateConstraintsFromJavaPairList(
|
|
|
|
|
jni, j_constraints, "optional", &optional_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual ~ConstraintsWrapper() {}
|
|
|
|
|
|
|
|
|
|
// MediaConstraintsInterface.
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual const Constraints& GetMandatory() const OVERRIDE {
|
|
|
|
|
return mandatory_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual const Constraints& GetOptional() const OVERRIDE {
|
|
|
|
|
return optional_;
|
|
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Helper for translating a List<Pair<String, String>> to a Constraints.
|
|
|
|
|
static void PopulateConstraintsFromJavaPairList(
|
|
|
|
|
JNIEnv* jni, jobject j_constraints,
|
|
|
|
|
const char* field_name, Constraints* field) {
|
|
|
|
|
jfieldID j_id = GetFieldID(jni,
|
|
|
|
|
GetObjectClass(jni, j_constraints), field_name, "Ljava/util/List;");
|
|
|
|
|
jobject j_list = GetObjectField(jni, j_constraints, j_id);
|
|
|
|
|
jmethodID j_iterator_id = GetMethodID(jni,
|
|
|
|
|
GetObjectClass(jni, j_list), "iterator", "()Ljava/util/Iterator;");
|
|
|
|
|
jobject j_iterator = jni->CallObjectMethod(j_list, j_iterator_id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID j_has_next = GetMethodID(jni,
|
|
|
|
|
GetObjectClass(jni, j_iterator), "hasNext", "()Z");
|
|
|
|
|
jmethodID j_next = GetMethodID(jni,
|
|
|
|
|
GetObjectClass(jni, j_iterator), "next", "()Ljava/lang/Object;");
|
|
|
|
|
while (jni->CallBooleanMethod(j_iterator, j_has_next)) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallBooleanMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jobject entry = jni->CallObjectMethod(j_iterator, j_next);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID get_key = GetMethodID(jni,
|
|
|
|
|
GetObjectClass(jni, entry), "getKey", "()Ljava/lang/String;");
|
|
|
|
|
jstring j_key = reinterpret_cast<jstring>(
|
|
|
|
|
jni->CallObjectMethod(entry, get_key));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID get_value = GetMethodID(jni,
|
|
|
|
|
GetObjectClass(jni, entry), "getValue", "()Ljava/lang/String;");
|
|
|
|
|
jstring j_value = reinterpret_cast<jstring>(
|
|
|
|
|
jni->CallObjectMethod(entry, get_value));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
field->push_back(Constraint(JavaToStdString(jni, j_key),
|
|
|
|
|
JavaToStdString(jni, j_value)));
|
|
|
|
|
}
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallBooleanMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Constraints mandatory_;
|
|
|
|
|
Constraints optional_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static jobject JavaSdpFromNativeSdp(
|
|
|
|
|
JNIEnv* jni, const SessionDescriptionInterface* desc) {
|
|
|
|
|
std::string sdp;
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(desc->ToString(&sdp)) << "got so far: " << sdp;
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring j_description = JavaStringFromStdString(jni, sdp);
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
jclass j_type_class = FindClass(
|
|
|
|
|
jni, "org/webrtc/SessionDescription$Type");
|
|
|
|
|
jmethodID j_type_from_canonical = GetStaticMethodID(
|
|
|
|
|
jni, j_type_class, "fromCanonicalForm",
|
|
|
|
|
"(Ljava/lang/String;)Lorg/webrtc/SessionDescription$Type;");
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring j_type_string = JavaStringFromStdString(jni, desc->type());
|
2013-07-10 00:45:36 +00:00
|
|
|
jobject j_type = jni->CallStaticObjectMethod(
|
2014-01-09 00:31:17 +00:00
|
|
|
j_type_class, j_type_from_canonical, j_type_string);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
|
|
|
|
|
jclass j_sdp_class = FindClass(jni, "org/webrtc/SessionDescription");
|
|
|
|
|
jmethodID j_sdp_ctor = GetMethodID(
|
|
|
|
|
jni, j_sdp_class, "<init>",
|
|
|
|
|
"(Lorg/webrtc/SessionDescription$Type;Ljava/lang/String;)V");
|
|
|
|
|
jobject j_sdp = jni->NewObject(
|
2014-01-09 00:31:17 +00:00
|
|
|
j_sdp_class, j_sdp_ctor, j_type, j_description);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewObject";
|
2013-07-10 00:45:36 +00:00
|
|
|
return j_sdp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class T> // T is one of {Create,Set}SessionDescriptionObserver.
|
|
|
|
|
class SdpObserverWrapper : public T {
|
|
|
|
|
public:
|
|
|
|
|
SdpObserverWrapper(JNIEnv* jni, jobject j_observer,
|
|
|
|
|
ConstraintsWrapper* constraints)
|
|
|
|
|
: constraints_(constraints),
|
2013-07-12 16:04:50 +00:00
|
|
|
j_observer_global_(jni, j_observer),
|
|
|
|
|
j_observer_class_(jni, GetObjectClass(jni, j_observer)) {
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual ~SdpObserverWrapper() {}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
// Can't mark OVERRIDE because of templating.
|
2013-07-10 00:45:36 +00:00
|
|
|
virtual void OnSuccess() {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onSetSuccess", "()V");
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
// Can't mark OVERRIDE because of templating.
|
2013-07-10 00:45:36 +00:00
|
|
|
virtual void OnSuccess(SessionDescriptionInterface* desc) {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID m = GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni(), *j_observer_class_, "onCreateSuccess",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Lorg/webrtc/SessionDescription;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject j_sdp = JavaSdpFromNativeSdp(jni(), desc);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_sdp);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
// Common implementation for failure of Set & Create types, distinguished by
|
|
|
|
|
// |op| being "Set" or "Create".
|
|
|
|
|
void OnFailure(const std::string& op, const std::string& error) {
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "on" + op + "Failure",
|
|
|
|
|
"(Ljava/lang/String;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jstring j_error_string = JavaStringFromStdString(jni(), error);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_error_string);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JNIEnv* jni() {
|
|
|
|
|
return AttachCurrentThreadIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-09 00:31:17 +00:00
|
|
|
private:
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<ConstraintsWrapper> constraints_;
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jobject> j_observer_global_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_observer_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class CreateSdpObserverWrapper
|
|
|
|
|
: public SdpObserverWrapper<CreateSessionDescriptionObserver> {
|
|
|
|
|
public:
|
|
|
|
|
CreateSdpObserverWrapper(JNIEnv* jni, jobject j_observer,
|
|
|
|
|
ConstraintsWrapper* constraints)
|
|
|
|
|
: SdpObserverWrapper(jni, j_observer, constraints) {}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnFailure(const std::string& error) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
SdpObserverWrapper::OnFailure(std::string("Create"), error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SetSdpObserverWrapper
|
|
|
|
|
: public SdpObserverWrapper<SetSessionDescriptionObserver> {
|
|
|
|
|
public:
|
|
|
|
|
SetSdpObserverWrapper(JNIEnv* jni, jobject j_observer,
|
|
|
|
|
ConstraintsWrapper* constraints)
|
|
|
|
|
: SdpObserverWrapper(jni, j_observer, constraints) {}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void OnFailure(const std::string& error) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
SdpObserverWrapper::OnFailure(std::string("Set"), error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
// Adapter for a Java DataChannel$Observer presenting a C++ DataChannelObserver
|
|
|
|
|
// and dispatching the callback from C++ back to Java.
|
|
|
|
|
class DataChannelObserverWrapper : public DataChannelObserver {
|
|
|
|
|
public:
|
|
|
|
|
DataChannelObserverWrapper(JNIEnv* jni, jobject j_observer)
|
|
|
|
|
: j_observer_global_(jni, j_observer),
|
|
|
|
|
j_observer_class_(jni, GetObjectClass(jni, j_observer)),
|
2014-08-26 11:08:06 +00:00
|
|
|
j_buffer_class_(jni, FindClass(jni, "org/webrtc/DataChannel$Buffer")),
|
2013-07-12 16:04:50 +00:00
|
|
|
j_on_state_change_mid_(GetMethodID(jni, *j_observer_class_,
|
|
|
|
|
"onStateChange", "()V")),
|
|
|
|
|
j_on_message_mid_(GetMethodID(jni, *j_observer_class_, "onMessage",
|
|
|
|
|
"(Lorg/webrtc/DataChannel$Buffer;)V")),
|
|
|
|
|
j_buffer_ctor_(GetMethodID(jni, *j_buffer_class_,
|
|
|
|
|
"<init>", "(Ljava/nio/ByteBuffer;Z)V")) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual ~DataChannelObserverWrapper() {}
|
|
|
|
|
|
|
|
|
|
virtual void OnStateChange() OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-12 16:04:50 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, j_on_state_change_mid_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual void OnMessage(const DataBuffer& buffer) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-12 16:04:50 +00:00
|
|
|
jobject byte_buffer =
|
|
|
|
|
jni()->NewDirectByteBuffer(const_cast<char*>(buffer.data.data()),
|
|
|
|
|
buffer.data.length());
|
|
|
|
|
jobject j_buffer = jni()->NewObject(*j_buffer_class_, j_buffer_ctor_,
|
|
|
|
|
byte_buffer, buffer.binary);
|
|
|
|
|
jni()->CallVoidMethod(*j_observer_global_, j_on_message_mid_, j_buffer);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
JNIEnv* jni() {
|
|
|
|
|
return AttachCurrentThreadIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ScopedGlobalRef<jobject> j_observer_global_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_observer_class_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_buffer_class_;
|
|
|
|
|
const jmethodID j_on_state_change_mid_;
|
|
|
|
|
const jmethodID j_on_message_mid_;
|
|
|
|
|
const jmethodID j_buffer_ctor_;
|
|
|
|
|
};
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
// Adapter for a Java StatsObserver presenting a C++ StatsObserver and
|
|
|
|
|
// dispatching the callback from C++ back to Java.
|
|
|
|
|
class StatsObserverWrapper : public StatsObserver {
|
|
|
|
|
public:
|
|
|
|
|
StatsObserverWrapper(JNIEnv* jni, jobject j_observer)
|
2013-07-12 16:04:50 +00:00
|
|
|
: j_observer_global_(jni, j_observer),
|
|
|
|
|
j_observer_class_(jni, GetObjectClass(jni, j_observer)),
|
|
|
|
|
j_stats_report_class_(jni, FindClass(jni, "org/webrtc/StatsReport")),
|
2013-07-10 00:45:36 +00:00
|
|
|
j_stats_report_ctor_(GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni, *j_stats_report_class_, "<init>",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Ljava/lang/String;Ljava/lang/String;D"
|
|
|
|
|
"[Lorg/webrtc/StatsReport$Value;)V")),
|
2013-07-12 16:04:50 +00:00
|
|
|
j_value_class_(jni, FindClass(
|
2013-07-10 00:45:36 +00:00
|
|
|
jni, "org/webrtc/StatsReport$Value")),
|
|
|
|
|
j_value_ctor_(GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni, *j_value_class_, "<init>",
|
2013-07-10 00:45:36 +00:00
|
|
|
"(Ljava/lang/String;Ljava/lang/String;)V")) {
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual ~StatsObserverWrapper() {}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2014-12-15 13:22:54 +00:00
|
|
|
virtual void OnComplete(const StatsReports& reports) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
|
|
|
|
jobjectArray j_reports = ReportsToJava(jni(), reports);
|
2013-07-12 16:04:50 +00:00
|
|
|
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onComplete",
|
|
|
|
|
"([Lorg/webrtc/StatsReport;)V");
|
2014-01-09 00:31:17 +00:00
|
|
|
jni()->CallVoidMethod(*j_observer_global_, m, j_reports);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
jobjectArray ReportsToJava(
|
2014-12-15 13:22:54 +00:00
|
|
|
JNIEnv* jni, const StatsReports& reports) {
|
2013-07-10 00:45:36 +00:00
|
|
|
jobjectArray reports_array = jni->NewObjectArray(
|
2013-07-12 16:04:50 +00:00
|
|
|
reports.size(), *j_stats_report_class_, NULL);
|
2014-12-15 13:22:54 +00:00
|
|
|
int i = 0;
|
|
|
|
|
for (const auto* report : reports) {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
2015-01-19 20:41:26 +00:00
|
|
|
jstring j_id = JavaStringFromStdString(jni, report->id().ToString());
|
|
|
|
|
jstring j_type = JavaStringFromStdString(jni, report->TypeToString());
|
|
|
|
|
jobjectArray j_values = ValuesToJava(jni, report->values());
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject j_report = jni->NewObject(*j_stats_report_class_,
|
|
|
|
|
j_stats_report_ctor_,
|
|
|
|
|
j_id,
|
|
|
|
|
j_type,
|
2015-01-19 20:41:26 +00:00
|
|
|
report->timestamp(),
|
2014-01-09 00:31:17 +00:00
|
|
|
j_values);
|
2014-12-15 13:22:54 +00:00
|
|
|
jni->SetObjectArrayElement(reports_array, i++, j_report);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
return reports_array;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jobjectArray ValuesToJava(JNIEnv* jni, const StatsReport::Values& values) {
|
|
|
|
|
jobjectArray j_values = jni->NewObjectArray(
|
2013-07-12 16:04:50 +00:00
|
|
|
values.size(), *j_value_class_, NULL);
|
2013-07-10 00:45:36 +00:00
|
|
|
for (int i = 0; i < values.size(); ++i) {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
2015-01-19 20:41:26 +00:00
|
|
|
const auto& value = values[i];
|
2014-12-12 17:41:28 +00:00
|
|
|
// Should we use the '.name' enum value here instead of converting the
|
|
|
|
|
// name to a string?
|
2015-01-19 20:41:26 +00:00
|
|
|
jstring j_name = JavaStringFromStdString(jni, value->display_name());
|
|
|
|
|
jstring j_value = JavaStringFromStdString(jni, value->value);
|
2014-01-09 00:31:17 +00:00
|
|
|
jobject j_element_value =
|
|
|
|
|
jni->NewObject(*j_value_class_, j_value_ctor_, j_name, j_value);
|
|
|
|
|
jni->SetObjectArrayElement(j_values, i, j_element_value);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
return j_values;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JNIEnv* jni() {
|
|
|
|
|
return AttachCurrentThreadIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jobject> j_observer_global_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_observer_class_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_stats_report_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
const jmethodID j_stats_report_ctor_;
|
2013-07-12 16:04:50 +00:00
|
|
|
const ScopedGlobalRef<jclass> j_value_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
const jmethodID j_value_ctor_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Adapter presenting a cricket::VideoRenderer as a
|
|
|
|
|
// webrtc::VideoRendererInterface.
|
|
|
|
|
class VideoRendererWrapper : public VideoRendererInterface {
|
|
|
|
|
public:
|
|
|
|
|
static VideoRendererWrapper* Create(cricket::VideoRenderer* renderer) {
|
|
|
|
|
if (renderer)
|
|
|
|
|
return new VideoRendererWrapper(renderer);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual ~VideoRendererWrapper() {}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void SetSize(int width, int height) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(AttachCurrentThreadIfNeeded());
|
2013-07-10 00:45:36 +00:00
|
|
|
const bool kNotReserved = false; // What does this param mean??
|
|
|
|
|
renderer_->SetSize(width, height, kNotReserved);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(AttachCurrentThreadIfNeeded());
|
2013-07-10 00:45:36 +00:00
|
|
|
renderer_->RenderFrame(frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
explicit VideoRendererWrapper(cricket::VideoRenderer* renderer)
|
|
|
|
|
: renderer_(renderer) {}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::VideoRenderer> renderer_;
|
2013-07-10 00:45:36 +00:00
|
|
|
};
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Wrapper for texture object in TextureVideoFrame.
|
|
|
|
|
class NativeHandleImpl : public NativeHandle {
|
|
|
|
|
public:
|
|
|
|
|
NativeHandleImpl() :
|
|
|
|
|
ref_count_(0), texture_object_(NULL), texture_id_(-1) {}
|
|
|
|
|
virtual ~NativeHandleImpl() {}
|
|
|
|
|
virtual int32_t AddRef() {
|
|
|
|
|
return ++ref_count_;
|
|
|
|
|
}
|
|
|
|
|
virtual int32_t Release() {
|
|
|
|
|
return --ref_count_;
|
|
|
|
|
}
|
|
|
|
|
virtual void* GetHandle() {
|
|
|
|
|
return texture_object_;
|
|
|
|
|
}
|
|
|
|
|
int GetTextureId() {
|
|
|
|
|
return texture_id_;
|
|
|
|
|
}
|
|
|
|
|
void SetTextureObject(void *texture_object, int texture_id) {
|
|
|
|
|
texture_object_ = reinterpret_cast<jobject>(texture_object);
|
|
|
|
|
texture_id_ = texture_id;
|
|
|
|
|
}
|
|
|
|
|
int32_t ref_count() {
|
|
|
|
|
return ref_count_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
int32_t ref_count_;
|
|
|
|
|
jobject texture_object_;
|
|
|
|
|
int32_t texture_id_;
|
|
|
|
|
};
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
// Wrapper dispatching webrtc::VideoRendererInterface to a Java VideoRenderer
|
|
|
|
|
// instance.
|
|
|
|
|
class JavaVideoRendererWrapper : public VideoRendererInterface {
|
|
|
|
|
public:
|
|
|
|
|
JavaVideoRendererWrapper(JNIEnv* jni, jobject j_callbacks)
|
2013-07-12 16:04:50 +00:00
|
|
|
: j_callbacks_(jni, j_callbacks),
|
|
|
|
|
j_set_size_id_(GetMethodID(
|
|
|
|
|
jni, GetObjectClass(jni, j_callbacks), "setSize", "(II)V")),
|
|
|
|
|
j_render_frame_id_(GetMethodID(
|
|
|
|
|
jni, GetObjectClass(jni, j_callbacks), "renderFrame",
|
|
|
|
|
"(Lorg/webrtc/VideoRenderer$I420Frame;)V")),
|
|
|
|
|
j_frame_class_(jni,
|
|
|
|
|
FindClass(jni, "org/webrtc/VideoRenderer$I420Frame")),
|
2014-09-15 17:52:42 +00:00
|
|
|
j_i420_frame_ctor_id_(GetMethodID(
|
2013-07-12 16:04:50 +00:00
|
|
|
jni, *j_frame_class_, "<init>", "(II[I[Ljava/nio/ByteBuffer;)V")),
|
2014-09-15 17:52:42 +00:00
|
|
|
j_texture_frame_ctor_id_(GetMethodID(
|
|
|
|
|
jni, *j_frame_class_, "<init>",
|
|
|
|
|
"(IILjava/lang/Object;I)V")),
|
2013-07-12 16:04:50 +00:00
|
|
|
j_byte_buffer_class_(jni, FindClass(jni, "java/nio/ByteBuffer")) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual ~JavaVideoRendererWrapper() {}
|
|
|
|
|
|
|
|
|
|
virtual void SetSize(int width, int height) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
jni()->CallVoidMethod(*j_callbacks_, j_set_size_id_, width, height);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni());
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE {
|
2014-01-09 00:31:17 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni());
|
2014-09-15 17:52:42 +00:00
|
|
|
if (frame->GetNativeHandle() != NULL) {
|
|
|
|
|
jobject j_frame = CricketToJavaTextureFrame(frame);
|
|
|
|
|
jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, j_frame);
|
|
|
|
|
CHECK_EXCEPTION(jni());
|
|
|
|
|
} else {
|
|
|
|
|
jobject j_frame = CricketToJavaI420Frame(frame);
|
|
|
|
|
jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, j_frame);
|
|
|
|
|
CHECK_EXCEPTION(jni());
|
|
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Return a VideoRenderer.I420Frame referring to the data in |frame|.
|
2014-09-15 17:52:42 +00:00
|
|
|
jobject CricketToJavaI420Frame(const cricket::VideoFrame* frame) {
|
2014-01-09 00:31:17 +00:00
|
|
|
jintArray strides = jni()->NewIntArray(3);
|
|
|
|
|
jint* strides_array = jni()->GetIntArrayElements(strides, NULL);
|
2013-07-10 00:45:36 +00:00
|
|
|
strides_array[0] = frame->GetYPitch();
|
|
|
|
|
strides_array[1] = frame->GetUPitch();
|
|
|
|
|
strides_array[2] = frame->GetVPitch();
|
2014-01-09 00:31:17 +00:00
|
|
|
jni()->ReleaseIntArrayElements(strides, strides_array, 0);
|
|
|
|
|
jobjectArray planes = jni()->NewObjectArray(3, *j_byte_buffer_class_, NULL);
|
|
|
|
|
jobject y_buffer = jni()->NewDirectByteBuffer(
|
2013-07-10 00:45:36 +00:00
|
|
|
const_cast<uint8*>(frame->GetYPlane()),
|
2014-01-09 00:31:17 +00:00
|
|
|
frame->GetYPitch() * frame->GetHeight());
|
|
|
|
|
jobject u_buffer = jni()->NewDirectByteBuffer(
|
|
|
|
|
const_cast<uint8*>(frame->GetUPlane()), frame->GetChromaSize());
|
|
|
|
|
jobject v_buffer = jni()->NewDirectByteBuffer(
|
|
|
|
|
const_cast<uint8*>(frame->GetVPlane()), frame->GetChromaSize());
|
|
|
|
|
jni()->SetObjectArrayElement(planes, 0, y_buffer);
|
|
|
|
|
jni()->SetObjectArrayElement(planes, 1, u_buffer);
|
|
|
|
|
jni()->SetObjectArrayElement(planes, 2, v_buffer);
|
2013-07-10 00:45:36 +00:00
|
|
|
return jni()->NewObject(
|
2014-09-15 17:52:42 +00:00
|
|
|
*j_frame_class_, j_i420_frame_ctor_id_,
|
2014-01-09 00:31:17 +00:00
|
|
|
frame->GetWidth(), frame->GetHeight(), strides, planes);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Return a VideoRenderer.I420Frame referring texture object in |frame|.
|
|
|
|
|
jobject CricketToJavaTextureFrame(const cricket::VideoFrame* frame) {
|
|
|
|
|
NativeHandleImpl* handle =
|
|
|
|
|
reinterpret_cast<NativeHandleImpl*>(frame->GetNativeHandle());
|
|
|
|
|
jobject texture_object = reinterpret_cast<jobject>(handle->GetHandle());
|
|
|
|
|
int texture_id = handle->GetTextureId();
|
|
|
|
|
return jni()->NewObject(
|
|
|
|
|
*j_frame_class_, j_texture_frame_ctor_id_,
|
|
|
|
|
frame->GetWidth(), frame->GetHeight(), texture_object, texture_id);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JNIEnv* jni() {
|
|
|
|
|
return AttachCurrentThreadIfNeeded();
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
ScopedGlobalRef<jobject> j_callbacks_;
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID j_set_size_id_;
|
|
|
|
|
jmethodID j_render_frame_id_;
|
2013-07-12 16:04:50 +00:00
|
|
|
ScopedGlobalRef<jclass> j_frame_class_;
|
2014-09-15 17:52:42 +00:00
|
|
|
jmethodID j_i420_frame_ctor_id_;
|
|
|
|
|
jmethodID j_texture_frame_ctor_id_;
|
2013-07-12 16:04:50 +00:00
|
|
|
ScopedGlobalRef<jclass> j_byte_buffer_class_;
|
2013-07-10 00:45:36 +00:00
|
|
|
};
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2014-02-13 03:56:14 +00:00
|
|
|
// TODO(fischman): consider pulling MediaCodecVideoEncoder out of this file and
|
|
|
|
|
// into its own .h/.cc pair, if/when the JNI helper stuff above is extracted
|
|
|
|
|
// from this file.
|
|
|
|
|
|
2014-06-13 22:59:08 +00:00
|
|
|
//#define TRACK_BUFFER_TIMING
|
2014-07-22 17:44:53 +00:00
|
|
|
#define TAG "MediaCodecVideo"
|
2014-06-13 22:59:08 +00:00
|
|
|
#ifdef TRACK_BUFFER_TIMING
|
|
|
|
|
#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
|
|
|
|
|
#else
|
|
|
|
|
#define ALOGV(...)
|
|
|
|
|
#endif
|
2014-07-22 17:44:53 +00:00
|
|
|
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
|
|
|
|
|
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
2014-06-13 22:59:08 +00:00
|
|
|
|
2014-06-10 23:48:29 +00:00
|
|
|
// Color formats supported by encoder - should mirror supportedColorList
|
|
|
|
|
// from MediaCodecVideoEncoder.java
|
|
|
|
|
enum COLOR_FORMATTYPE {
|
|
|
|
|
COLOR_FormatYUV420Planar = 0x13,
|
|
|
|
|
COLOR_FormatYUV420SemiPlanar = 0x15,
|
|
|
|
|
COLOR_QCOM_FormatYUV420SemiPlanar = 0x7FA30C00,
|
|
|
|
|
// NV12 color format supported by QCOM codec, but not declared in MediaCodec -
|
|
|
|
|
// see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
|
|
|
|
|
// This format is presumably similar to COLOR_FormatYUV420SemiPlanar,
|
|
|
|
|
// but requires some (16, 32?) byte alignment.
|
|
|
|
|
COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04
|
|
|
|
|
};
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
// Arbitrary interval to poll the codec for new outputs.
|
|
|
|
|
enum { kMediaCodecPollMs = 10 };
|
2014-09-15 17:52:42 +00:00
|
|
|
// Media codec maximum output buffer ready timeout.
|
|
|
|
|
enum { kMediaCodecTimeoutMs = 500 };
|
|
|
|
|
// Interval to print codec statistics (bitrate, fps, encoding/decoding time).
|
|
|
|
|
enum { kMediaCodecStatisticsIntervalMs = 3000 };
|
|
|
|
|
|
|
|
|
|
static int64_t GetCurrentTimeMs() {
|
|
|
|
|
return TickTime::Now().Ticks() / 1000000LL;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
|
2014-10-07 17:11:36 +00:00
|
|
|
// Allow Invoke() calls from from current thread.
|
|
|
|
|
static void AllowBlockingCalls() {
|
|
|
|
|
Thread* current_thread = Thread::Current();
|
|
|
|
|
if (current_thread != NULL)
|
|
|
|
|
current_thread->SetAllowBlockingCalls(true);
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
// MediaCodecVideoEncoder is a webrtc::VideoEncoder implementation that uses
|
|
|
|
|
// Android's MediaCodec SDK API behind the scenes to implement (hopefully)
|
|
|
|
|
// HW-backed video encode. This C++ class is implemented as a very thin shim,
|
|
|
|
|
// delegating all of the interesting work to org.webrtc.MediaCodecVideoEncoder.
|
|
|
|
|
// MediaCodecVideoEncoder is created, operated, and destroyed on a single
|
|
|
|
|
// thread, currently the libjingle Worker thread.
|
|
|
|
|
class MediaCodecVideoEncoder : public webrtc::VideoEncoder,
|
2014-07-29 17:36:52 +00:00
|
|
|
public rtc::MessageHandler {
|
2014-02-13 03:56:14 +00:00
|
|
|
public:
|
|
|
|
|
virtual ~MediaCodecVideoEncoder();
|
|
|
|
|
explicit MediaCodecVideoEncoder(JNIEnv* jni);
|
|
|
|
|
|
|
|
|
|
// webrtc::VideoEncoder implementation. Everything trampolines to
|
|
|
|
|
// |codec_thread_| for execution.
|
|
|
|
|
virtual int32_t InitEncode(const webrtc::VideoCodec* codec_settings,
|
|
|
|
|
int32_t /* number_of_cores */,
|
Use size_t more consistently for packet/payload lengths.
See design doc at https://docs.google.com/a/chromium.org/document/d/1I6nmE9D_BmCY-IoV6MDPY2V6WYpEI-dg2apWXTfZyUI/edit?usp=sharing for more information.
This CL was reviewed and approved in pieces in the following CLs:
https://webrtc-codereview.appspot.com/24209004/
https://webrtc-codereview.appspot.com/24229004/
https://webrtc-codereview.appspot.com/24259004/
https://webrtc-codereview.appspot.com/25109004/
https://webrtc-codereview.appspot.com/26099004/
https://webrtc-codereview.appspot.com/27069004/
https://webrtc-codereview.appspot.com/27969004/
https://webrtc-codereview.appspot.com/27989004/
https://webrtc-codereview.appspot.com/29009004/
https://webrtc-codereview.appspot.com/30929004/
https://webrtc-codereview.appspot.com/30939004/
https://webrtc-codereview.appspot.com/31999004/
Committing as TBR to the original reviewers.
BUG=chromium:81439
TEST=none
TBR=pthatcher,henrik.lundin,tina.legrand,stefan,tkchin,glaznev,kjellander,perkj,mflodman,henrika,asapersson,niklas.enbom
Review URL: https://webrtc-codereview.appspot.com/23129004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@7726 4adac7df-926f-26a2-2b94-8c16560cd09d
2014-11-20 22:28:14 +00:00
|
|
|
size_t /* max_payload_size */) OVERRIDE;
|
2014-02-13 03:56:14 +00:00
|
|
|
virtual int32_t Encode(
|
|
|
|
|
const webrtc::I420VideoFrame& input_image,
|
|
|
|
|
const webrtc::CodecSpecificInfo* /* codec_specific_info */,
|
|
|
|
|
const std::vector<webrtc::VideoFrameType>* frame_types) OVERRIDE;
|
|
|
|
|
virtual int32_t RegisterEncodeCompleteCallback(
|
|
|
|
|
webrtc::EncodedImageCallback* callback) OVERRIDE;
|
|
|
|
|
virtual int32_t Release() OVERRIDE;
|
|
|
|
|
virtual int32_t SetChannelParameters(uint32_t /* packet_loss */,
|
2015-01-12 21:51:21 +00:00
|
|
|
int64_t /* rtt */) OVERRIDE;
|
2014-02-13 03:56:14 +00:00
|
|
|
virtual int32_t SetRates(uint32_t new_bit_rate, uint32_t frame_rate) OVERRIDE;
|
|
|
|
|
|
2014-07-29 17:36:52 +00:00
|
|
|
// rtc::MessageHandler implementation.
|
|
|
|
|
virtual void OnMessage(rtc::Message* msg) OVERRIDE;
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// CHECK-fail if not running on |codec_thread_|.
|
|
|
|
|
void CheckOnCodecThread();
|
|
|
|
|
|
|
|
|
|
// Release() and InitEncode() in an attempt to restore the codec to an
|
|
|
|
|
// operable state. Necessary after all manner of OMX-layer errors.
|
|
|
|
|
void ResetCodec();
|
|
|
|
|
|
|
|
|
|
// Implementation of webrtc::VideoEncoder methods above, all running on the
|
|
|
|
|
// codec thread exclusively.
|
|
|
|
|
//
|
|
|
|
|
// If width==0 then this is assumed to be a re-initialization and the
|
|
|
|
|
// previously-current values are reused instead of the passed parameters
|
|
|
|
|
// (makes it easier to reason about thread-safety).
|
2014-06-13 22:59:08 +00:00
|
|
|
int32_t InitEncodeOnCodecThread(int width, int height, int kbps, int fps);
|
2014-02-13 03:56:14 +00:00
|
|
|
int32_t EncodeOnCodecThread(
|
|
|
|
|
const webrtc::I420VideoFrame& input_image,
|
|
|
|
|
const std::vector<webrtc::VideoFrameType>* frame_types);
|
|
|
|
|
int32_t RegisterEncodeCompleteCallbackOnCodecThread(
|
|
|
|
|
webrtc::EncodedImageCallback* callback);
|
|
|
|
|
int32_t ReleaseOnCodecThread();
|
|
|
|
|
int32_t SetRatesOnCodecThread(uint32_t new_bit_rate, uint32_t frame_rate);
|
|
|
|
|
|
|
|
|
|
// Helper accessors for MediaCodecVideoEncoder$OutputBufferInfo members.
|
|
|
|
|
int GetOutputBufferInfoIndex(JNIEnv* jni, jobject j_output_buffer_info);
|
|
|
|
|
jobject GetOutputBufferInfoBuffer(JNIEnv* jni, jobject j_output_buffer_info);
|
|
|
|
|
bool GetOutputBufferInfoIsKeyFrame(JNIEnv* jni, jobject j_output_buffer_info);
|
|
|
|
|
jlong GetOutputBufferInfoPresentationTimestampUs(
|
|
|
|
|
JNIEnv* jni,
|
|
|
|
|
jobject j_output_buffer_info);
|
|
|
|
|
|
|
|
|
|
// Deliver any outputs pending in the MediaCodec to our |callback_| and return
|
|
|
|
|
// true on success.
|
|
|
|
|
bool DeliverPendingOutputs(JNIEnv* jni);
|
|
|
|
|
|
|
|
|
|
// Valid all the time since RegisterEncodeCompleteCallback() Invoke()s to
|
|
|
|
|
// |codec_thread_| synchronously.
|
|
|
|
|
webrtc::EncodedImageCallback* callback_;
|
|
|
|
|
|
|
|
|
|
// State that is constant for the lifetime of this object once the ctor
|
|
|
|
|
// returns.
|
|
|
|
|
scoped_ptr<Thread> codec_thread_; // Thread on which to operate MediaCodec.
|
|
|
|
|
ScopedGlobalRef<jclass> j_media_codec_video_encoder_class_;
|
|
|
|
|
ScopedGlobalRef<jobject> j_media_codec_video_encoder_;
|
|
|
|
|
jmethodID j_init_encode_method_;
|
|
|
|
|
jmethodID j_dequeue_input_buffer_method_;
|
|
|
|
|
jmethodID j_encode_method_;
|
|
|
|
|
jmethodID j_release_method_;
|
|
|
|
|
jmethodID j_set_rates_method_;
|
|
|
|
|
jmethodID j_dequeue_output_buffer_method_;
|
|
|
|
|
jmethodID j_release_output_buffer_method_;
|
2014-06-10 23:48:29 +00:00
|
|
|
jfieldID j_color_format_field_;
|
2014-02-13 03:56:14 +00:00
|
|
|
jfieldID j_info_index_field_;
|
|
|
|
|
jfieldID j_info_buffer_field_;
|
|
|
|
|
jfieldID j_info_is_key_frame_field_;
|
|
|
|
|
jfieldID j_info_presentation_timestamp_us_field_;
|
|
|
|
|
|
|
|
|
|
// State that is valid only between InitEncode() and the next Release().
|
|
|
|
|
// Touched only on codec_thread_ so no explicit synchronization necessary.
|
|
|
|
|
int width_; // Frame width in pixels.
|
|
|
|
|
int height_; // Frame height in pixels.
|
2014-07-22 17:44:53 +00:00
|
|
|
bool inited_;
|
2014-10-09 17:53:09 +00:00
|
|
|
uint16_t picture_id_;
|
2014-06-10 23:48:29 +00:00
|
|
|
enum libyuv::FourCC encoder_fourcc_; // Encoder color space format.
|
2014-02-13 03:56:14 +00:00
|
|
|
int last_set_bitrate_kbps_; // Last-requested bitrate in kbps.
|
2014-06-13 22:59:08 +00:00
|
|
|
int last_set_fps_; // Last-requested frame rate.
|
2014-09-15 17:52:42 +00:00
|
|
|
int64_t current_timestamp_us_; // Current frame timestamps in us.
|
|
|
|
|
int frames_received_; // Number of frames received by encoder.
|
|
|
|
|
int frames_dropped_; // Number of frames dropped by encoder.
|
2014-09-18 23:01:03 +00:00
|
|
|
int frames_resolution_update_; // Number of frames with new codec resolution.
|
2014-09-15 17:52:42 +00:00
|
|
|
int frames_in_queue_; // Number of frames in encoder queue.
|
|
|
|
|
int64_t start_time_ms_; // Start time for statistics.
|
|
|
|
|
int current_frames_; // Number of frames in the current statistics interval.
|
|
|
|
|
int current_bytes_; // Encoded bytes in the current statistics interval.
|
|
|
|
|
int current_encoding_time_ms_; // Overall encoding time in the current second
|
|
|
|
|
int64_t last_input_timestamp_ms_; // Timestamp of last received yuv frame.
|
|
|
|
|
int64_t last_output_timestamp_ms_; // Timestamp of last encoded frame.
|
|
|
|
|
std::vector<int32_t> timestamps_; // Video frames timestamp queue.
|
|
|
|
|
std::vector<int64_t> render_times_ms_; // Video frames render time queue.
|
|
|
|
|
std::vector<int64_t> frame_rtc_times_ms_; // Time when video frame is sent to
|
|
|
|
|
// encoder input.
|
2014-06-10 23:48:29 +00:00
|
|
|
// Frame size in bytes fed to MediaCodec.
|
|
|
|
|
int yuv_size_;
|
2014-02-13 03:56:14 +00:00
|
|
|
// True only when between a callback_->Encoded() call return a positive value
|
|
|
|
|
// and the next Encode() call being ignored.
|
|
|
|
|
bool drop_next_input_frame_;
|
|
|
|
|
// Global references; must be deleted in Release().
|
|
|
|
|
std::vector<jobject> input_buffers_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoEncoder::~MediaCodecVideoEncoder() {
|
2014-09-18 23:01:03 +00:00
|
|
|
// Call Release() to ensure no more callbacks to us after we are deleted.
|
|
|
|
|
Release();
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni)
|
2014-09-18 23:01:03 +00:00
|
|
|
: callback_(NULL),
|
|
|
|
|
inited_(false),
|
2014-10-09 17:53:09 +00:00
|
|
|
picture_id_(0),
|
2014-09-18 23:01:03 +00:00
|
|
|
codec_thread_(new Thread()),
|
|
|
|
|
j_media_codec_video_encoder_class_(
|
|
|
|
|
jni,
|
|
|
|
|
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")),
|
|
|
|
|
j_media_codec_video_encoder_(
|
|
|
|
|
jni,
|
|
|
|
|
jni->NewObject(*j_media_codec_video_encoder_class_,
|
|
|
|
|
GetMethodID(jni,
|
|
|
|
|
*j_media_codec_video_encoder_class_,
|
|
|
|
|
"<init>",
|
|
|
|
|
"()V"))) {
|
2014-02-13 03:56:14 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
// It would be nice to avoid spinning up a new thread per MediaCodec, and
|
|
|
|
|
// instead re-use e.g. the PeerConnectionFactory's |worker_thread_|, but bug
|
|
|
|
|
// 2732 means that deadlocks abound. This class synchronously trampolines
|
|
|
|
|
// to |codec_thread_|, so if anything else can be coming to _us_ from
|
|
|
|
|
// |codec_thread_|, or from any thread holding the |_sendCritSect| described
|
|
|
|
|
// in the bug, we have a problem. For now work around that with a dedicated
|
|
|
|
|
// thread.
|
|
|
|
|
codec_thread_->SetName("MediaCodecVideoEncoder", NULL);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(codec_thread_->Start()) << "Failed to start MediaCodecVideoEncoder";
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
jclass j_output_buffer_info_class =
|
|
|
|
|
FindClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
|
|
|
|
|
j_init_encode_method_ = GetMethodID(jni,
|
|
|
|
|
*j_media_codec_video_encoder_class_,
|
|
|
|
|
"initEncode",
|
2014-06-13 22:59:08 +00:00
|
|
|
"(IIII)[Ljava/nio/ByteBuffer;");
|
2014-02-13 03:56:14 +00:00
|
|
|
j_dequeue_input_buffer_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_encoder_class_, "dequeueInputBuffer", "()I");
|
|
|
|
|
j_encode_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_encoder_class_, "encode", "(ZIIJ)Z");
|
|
|
|
|
j_release_method_ =
|
|
|
|
|
GetMethodID(jni, *j_media_codec_video_encoder_class_, "release", "()V");
|
|
|
|
|
j_set_rates_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_encoder_class_, "setRates", "(II)Z");
|
|
|
|
|
j_dequeue_output_buffer_method_ =
|
|
|
|
|
GetMethodID(jni,
|
|
|
|
|
*j_media_codec_video_encoder_class_,
|
|
|
|
|
"dequeueOutputBuffer",
|
|
|
|
|
"()Lorg/webrtc/MediaCodecVideoEncoder$OutputBufferInfo;");
|
|
|
|
|
j_release_output_buffer_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_encoder_class_, "releaseOutputBuffer", "(I)Z");
|
|
|
|
|
|
2014-06-10 23:48:29 +00:00
|
|
|
j_color_format_field_ =
|
|
|
|
|
GetFieldID(jni, *j_media_codec_video_encoder_class_, "colorFormat", "I");
|
2014-02-13 03:56:14 +00:00
|
|
|
j_info_index_field_ =
|
|
|
|
|
GetFieldID(jni, j_output_buffer_info_class, "index", "I");
|
|
|
|
|
j_info_buffer_field_ = GetFieldID(
|
|
|
|
|
jni, j_output_buffer_info_class, "buffer", "Ljava/nio/ByteBuffer;");
|
|
|
|
|
j_info_is_key_frame_field_ =
|
|
|
|
|
GetFieldID(jni, j_output_buffer_info_class, "isKeyFrame", "Z");
|
|
|
|
|
j_info_presentation_timestamp_us_field_ = GetFieldID(
|
|
|
|
|
jni, j_output_buffer_info_class, "presentationTimestampUs", "J");
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "MediaCodecVideoEncoder ctor failed";
|
2014-10-07 17:11:36 +00:00
|
|
|
AllowBlockingCalls();
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::InitEncode(
|
|
|
|
|
const webrtc::VideoCodec* codec_settings,
|
|
|
|
|
int32_t /* number_of_cores */,
|
Use size_t more consistently for packet/payload lengths.
See design doc at https://docs.google.com/a/chromium.org/document/d/1I6nmE9D_BmCY-IoV6MDPY2V6WYpEI-dg2apWXTfZyUI/edit?usp=sharing for more information.
This CL was reviewed and approved in pieces in the following CLs:
https://webrtc-codereview.appspot.com/24209004/
https://webrtc-codereview.appspot.com/24229004/
https://webrtc-codereview.appspot.com/24259004/
https://webrtc-codereview.appspot.com/25109004/
https://webrtc-codereview.appspot.com/26099004/
https://webrtc-codereview.appspot.com/27069004/
https://webrtc-codereview.appspot.com/27969004/
https://webrtc-codereview.appspot.com/27989004/
https://webrtc-codereview.appspot.com/29009004/
https://webrtc-codereview.appspot.com/30929004/
https://webrtc-codereview.appspot.com/30939004/
https://webrtc-codereview.appspot.com/31999004/
Committing as TBR to the original reviewers.
BUG=chromium:81439
TEST=none
TBR=pthatcher,henrik.lundin,tina.legrand,stefan,tkchin,glaznev,kjellander,perkj,mflodman,henrika,asapersson,niklas.enbom
Review URL: https://webrtc-codereview.appspot.com/23129004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@7726 4adac7df-926f-26a2-2b94-8c16560cd09d
2014-11-20 22:28:14 +00:00
|
|
|
size_t /* max_payload_size */) {
|
2014-02-13 03:56:14 +00:00
|
|
|
// Factory should guard against other codecs being used with us.
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(codec_settings->codecType == kVideoCodecVP8) << "Unsupported codec";
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread,
|
|
|
|
|
this,
|
|
|
|
|
codec_settings->width,
|
|
|
|
|
codec_settings->height,
|
2014-06-13 22:59:08 +00:00
|
|
|
codec_settings->startBitrate,
|
|
|
|
|
codec_settings->maxFramerate));
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::Encode(
|
|
|
|
|
const webrtc::I420VideoFrame& frame,
|
|
|
|
|
const webrtc::CodecSpecificInfo* /* codec_specific_info */,
|
|
|
|
|
const std::vector<webrtc::VideoFrameType>* frame_types) {
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(Bind(
|
|
|
|
|
&MediaCodecVideoEncoder::EncodeOnCodecThread, this, frame, frame_types));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallback(
|
|
|
|
|
webrtc::EncodedImageCallback* callback) {
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread,
|
|
|
|
|
this,
|
|
|
|
|
callback));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::Release() {
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoEncoder::ReleaseOnCodecThread, this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::SetChannelParameters(uint32_t /* packet_loss */,
|
2015-01-12 21:51:21 +00:00
|
|
|
int64_t /* rtt */) {
|
2014-02-13 03:56:14 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::SetRates(uint32_t new_bit_rate,
|
|
|
|
|
uint32_t frame_rate) {
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoEncoder::SetRatesOnCodecThread,
|
|
|
|
|
this,
|
|
|
|
|
new_bit_rate,
|
|
|
|
|
frame_rate));
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-29 17:36:52 +00:00
|
|
|
void MediaCodecVideoEncoder::OnMessage(rtc::Message* msg) {
|
2014-02-13 03:56:14 +00:00
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
|
|
|
|
|
// We only ever send one message to |this| directly (not through a Bind()'d
|
|
|
|
|
// functor), so expect no ID/data.
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(!msg->message_id) << "Unexpected message!";
|
|
|
|
|
CHECK(!msg->pdata) << "Unexpected message!";
|
2014-02-13 03:56:14 +00:00
|
|
|
CheckOnCodecThread();
|
2014-09-18 23:01:03 +00:00
|
|
|
if (!inited_) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
// It would be nice to recover from a failure here if one happened, but it's
|
|
|
|
|
// unclear how to signal such a failure to the app, so instead we stay silent
|
|
|
|
|
// about it and let the next app-called API method reveal the borkedness.
|
|
|
|
|
DeliverPendingOutputs(jni);
|
|
|
|
|
codec_thread_->PostDelayed(kMediaCodecPollMs, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaCodecVideoEncoder::CheckOnCodecThread() {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread())
|
|
|
|
|
<< "Running on wrong thread!";
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaCodecVideoEncoder::ResetCodec() {
|
2014-07-22 17:44:53 +00:00
|
|
|
ALOGE("ResetCodec");
|
2014-02-13 03:56:14 +00:00
|
|
|
if (Release() != WEBRTC_VIDEO_CODEC_OK ||
|
|
|
|
|
codec_thread_->Invoke<int32_t>(Bind(
|
2014-09-18 23:01:03 +00:00
|
|
|
&MediaCodecVideoEncoder::InitEncodeOnCodecThread, this,
|
|
|
|
|
width_, height_, 0, 0)) != WEBRTC_VIDEO_CODEC_OK) {
|
2014-02-13 03:56:14 +00:00
|
|
|
// TODO(fischman): wouldn't it be nice if there was a way to gracefully
|
|
|
|
|
// degrade to a SW encoder at this point? There isn't one AFAICT :(
|
|
|
|
|
// https://code.google.com/p/webrtc/issues/detail?id=2920
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread(
|
2014-06-13 22:59:08 +00:00
|
|
|
int width, int height, int kbps, int fps) {
|
2014-02-13 03:56:14 +00:00
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
|
2014-09-18 23:01:03 +00:00
|
|
|
ALOGD("InitEncodeOnCodecThread %d x %d. Bitrate: %d kbps. Fps: %d",
|
|
|
|
|
width, height, kbps, fps);
|
|
|
|
|
if (kbps == 0) {
|
2014-02-13 03:56:14 +00:00
|
|
|
kbps = last_set_bitrate_kbps_;
|
2014-09-18 23:01:03 +00:00
|
|
|
}
|
|
|
|
|
if (fps == 0) {
|
2014-06-13 22:59:08 +00:00
|
|
|
fps = last_set_fps_;
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
width_ = width;
|
|
|
|
|
height_ = height;
|
|
|
|
|
last_set_bitrate_kbps_ = kbps;
|
2014-06-13 22:59:08 +00:00
|
|
|
last_set_fps_ = fps;
|
2014-06-10 23:48:29 +00:00
|
|
|
yuv_size_ = width_ * height_ * 3 / 2;
|
|
|
|
|
frames_received_ = 0;
|
|
|
|
|
frames_dropped_ = 0;
|
2014-09-18 23:01:03 +00:00
|
|
|
frames_resolution_update_ = 0;
|
2014-06-13 22:59:08 +00:00
|
|
|
frames_in_queue_ = 0;
|
2014-09-15 17:52:42 +00:00
|
|
|
current_timestamp_us_ = 0;
|
|
|
|
|
start_time_ms_ = GetCurrentTimeMs();
|
|
|
|
|
current_frames_ = 0;
|
|
|
|
|
current_bytes_ = 0;
|
|
|
|
|
current_encoding_time_ms_ = 0;
|
2014-06-13 22:59:08 +00:00
|
|
|
last_input_timestamp_ms_ = -1;
|
|
|
|
|
last_output_timestamp_ms_ = -1;
|
2014-09-15 17:52:42 +00:00
|
|
|
timestamps_.clear();
|
|
|
|
|
render_times_ms_.clear();
|
|
|
|
|
frame_rtc_times_ms_.clear();
|
2014-09-18 23:01:03 +00:00
|
|
|
drop_next_input_frame_ = false;
|
2014-10-09 17:53:09 +00:00
|
|
|
picture_id_ = static_cast<uint16_t>(rand()) & 0x7FFF;
|
2014-02-13 03:56:14 +00:00
|
|
|
// We enforce no extra stride/padding in the format creation step.
|
|
|
|
|
jobjectArray input_buffers = reinterpret_cast<jobjectArray>(
|
|
|
|
|
jni->CallObjectMethod(*j_media_codec_video_encoder_,
|
|
|
|
|
j_init_encode_method_,
|
|
|
|
|
width_,
|
|
|
|
|
height_,
|
2014-06-13 22:59:08 +00:00
|
|
|
kbps,
|
|
|
|
|
fps));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-02-13 03:56:14 +00:00
|
|
|
if (IsNull(jni, input_buffers))
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
inited_ = true;
|
2014-06-10 23:48:29 +00:00
|
|
|
switch (GetIntField(jni, *j_media_codec_video_encoder_,
|
|
|
|
|
j_color_format_field_)) {
|
|
|
|
|
case COLOR_FormatYUV420Planar:
|
|
|
|
|
encoder_fourcc_ = libyuv::FOURCC_YU12;
|
|
|
|
|
break;
|
|
|
|
|
case COLOR_FormatYUV420SemiPlanar:
|
|
|
|
|
case COLOR_QCOM_FormatYUV420SemiPlanar:
|
|
|
|
|
case COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m:
|
|
|
|
|
encoder_fourcc_ = libyuv::FOURCC_NV12;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
LOG(LS_ERROR) << "Wrong color format.";
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
size_t num_input_buffers = jni->GetArrayLength(input_buffers);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(input_buffers_.empty())
|
|
|
|
|
<< "Unexpected double InitEncode without Release";
|
2014-02-13 03:56:14 +00:00
|
|
|
input_buffers_.resize(num_input_buffers);
|
|
|
|
|
for (size_t i = 0; i < num_input_buffers; ++i) {
|
|
|
|
|
input_buffers_[i] =
|
|
|
|
|
jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i));
|
2014-06-10 23:48:29 +00:00
|
|
|
int64 yuv_buffer_capacity =
|
2014-02-13 03:56:14 +00:00
|
|
|
jni->GetDirectBufferCapacity(input_buffers_[i]);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
CHECK(yuv_buffer_capacity >= yuv_size_) << "Insufficient capacity";
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
codec_thread_->PostDelayed(kMediaCodecPollMs, this);
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::EncodeOnCodecThread(
|
|
|
|
|
const webrtc::I420VideoFrame& frame,
|
|
|
|
|
const std::vector<webrtc::VideoFrameType>* frame_types) {
|
|
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
|
2014-09-18 23:01:03 +00:00
|
|
|
if (!inited_) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
|
|
|
|
}
|
2014-06-10 23:48:29 +00:00
|
|
|
frames_received_++;
|
2014-02-13 03:56:14 +00:00
|
|
|
if (!DeliverPendingOutputs(jni)) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
// Continue as if everything's fine.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (drop_next_input_frame_) {
|
2014-09-15 17:52:42 +00:00
|
|
|
ALOGV("Encoder drop frame - failed callback.");
|
2014-02-13 03:56:14 +00:00
|
|
|
drop_next_input_frame_ = false;
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(frame_types->size() == 1) << "Unexpected stream count";
|
2014-09-18 23:01:03 +00:00
|
|
|
if (frame.width() != width_ || frame.height() != height_) {
|
|
|
|
|
frames_resolution_update_++;
|
|
|
|
|
ALOGD("Unexpected frame resolution change from %d x %d to %d x %d",
|
|
|
|
|
width_, height_, frame.width(), frame.height());
|
|
|
|
|
if (frames_resolution_update_ > 3) {
|
|
|
|
|
// Reset codec if we received more than 3 frames with new resolution.
|
|
|
|
|
width_ = frame.width();
|
|
|
|
|
height_ = frame.height();
|
|
|
|
|
frames_resolution_update_ = 0;
|
|
|
|
|
ResetCodec();
|
|
|
|
|
}
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
frames_resolution_update_ = 0;
|
2014-02-13 03:56:14 +00:00
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
bool key_frame = frame_types->front() != webrtc::kDeltaFrame;
|
|
|
|
|
|
2014-06-13 22:59:08 +00:00
|
|
|
// Check if we accumulated too many frames in encoder input buffers
|
2014-09-15 17:52:42 +00:00
|
|
|
// or the encoder latency exceeds 70 ms and drop frame if so.
|
|
|
|
|
if (frames_in_queue_ > 0 && last_input_timestamp_ms_ >= 0) {
|
2014-06-13 22:59:08 +00:00
|
|
|
int encoder_latency_ms = last_input_timestamp_ms_ -
|
|
|
|
|
last_output_timestamp_ms_;
|
2014-09-15 17:52:42 +00:00
|
|
|
if (frames_in_queue_ > 2 || encoder_latency_ms > 70) {
|
2014-10-09 17:53:09 +00:00
|
|
|
ALOGD("Drop frame - encoder is behind by %d ms. Q size: %d",
|
2014-06-13 22:59:08 +00:00
|
|
|
encoder_latency_ms, frames_in_queue_);
|
|
|
|
|
frames_dropped_++;
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_,
|
|
|
|
|
j_dequeue_input_buffer_method_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-06-10 23:48:29 +00:00
|
|
|
if (j_input_buffer_index == -1) {
|
|
|
|
|
// Video codec falls behind - no input buffer available.
|
2014-09-15 17:52:42 +00:00
|
|
|
ALOGV("Encoder drop frame - no input buffers available");
|
2014-06-10 23:48:29 +00:00
|
|
|
frames_dropped_++;
|
2014-02-13 03:56:14 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887.
|
2014-06-10 23:48:29 +00:00
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
if (j_input_buffer_index == -2) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
ALOGV("Encode frame # %d. Buffer # %d. TS: %lld.",
|
2014-09-15 17:52:42 +00:00
|
|
|
frames_received_, j_input_buffer_index, current_timestamp_us_ / 1000);
|
2014-06-13 22:59:08 +00:00
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
jobject j_input_buffer = input_buffers_[j_input_buffer_index];
|
2014-06-10 23:48:29 +00:00
|
|
|
uint8* yuv_buffer =
|
2014-02-13 03:56:14 +00:00
|
|
|
reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
CHECK(yuv_buffer) << "Indirect buffer??";
|
2014-06-10 23:48:29 +00:00
|
|
|
CHECK(!libyuv::ConvertFromI420(
|
2014-09-15 17:52:42 +00:00
|
|
|
frame.buffer(webrtc::kYPlane), frame.stride(webrtc::kYPlane),
|
|
|
|
|
frame.buffer(webrtc::kUPlane), frame.stride(webrtc::kUPlane),
|
|
|
|
|
frame.buffer(webrtc::kVPlane), frame.stride(webrtc::kVPlane),
|
|
|
|
|
yuv_buffer, width_,
|
|
|
|
|
width_, height_,
|
|
|
|
|
encoder_fourcc_))
|
2014-09-09 11:45:44 +00:00
|
|
|
<< "ConvertFromI420 failed";
|
2014-09-15 17:52:42 +00:00
|
|
|
last_input_timestamp_ms_ = current_timestamp_us_ / 1000;
|
2014-06-13 22:59:08 +00:00
|
|
|
frames_in_queue_++;
|
2014-09-15 17:52:42 +00:00
|
|
|
|
|
|
|
|
// Save input image timestamps for later output
|
|
|
|
|
timestamps_.push_back(frame.timestamp());
|
|
|
|
|
render_times_ms_.push_back(frame.render_time_ms());
|
|
|
|
|
frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
|
|
|
|
|
j_encode_method_,
|
|
|
|
|
key_frame,
|
|
|
|
|
j_input_buffer_index,
|
2014-06-10 23:48:29 +00:00
|
|
|
yuv_size_,
|
2014-09-15 17:52:42 +00:00
|
|
|
current_timestamp_us_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-09-15 17:52:42 +00:00
|
|
|
current_timestamp_us_ += 1000000 / last_set_fps_;
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
if (!encode_status || !DeliverPendingOutputs(jni)) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread(
|
|
|
|
|
webrtc::EncodedImageCallback* callback) {
|
|
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
callback_ = callback;
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() {
|
2014-09-18 23:01:03 +00:00
|
|
|
if (!inited_) {
|
2014-07-22 17:44:53 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
2014-09-18 23:01:03 +00:00
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
2014-07-22 17:44:53 +00:00
|
|
|
ALOGD("EncoderRelease: Frames received: %d. Frames dropped: %d.",
|
|
|
|
|
frames_received_,frames_dropped_);
|
2014-02-13 03:56:14 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
for (size_t i = 0; i < input_buffers_.size(); ++i)
|
|
|
|
|
jni->DeleteGlobalRef(input_buffers_[i]);
|
|
|
|
|
input_buffers_.clear();
|
|
|
|
|
jni->CallVoidMethod(*j_media_codec_video_encoder_, j_release_method_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-09-18 23:01:03 +00:00
|
|
|
rtc::MessageQueueManager::Clear(this);
|
|
|
|
|
inited_ = false;
|
2014-02-13 03:56:14 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate,
|
|
|
|
|
uint32_t frame_rate) {
|
|
|
|
|
CheckOnCodecThread();
|
2014-06-13 22:59:08 +00:00
|
|
|
if (last_set_bitrate_kbps_ == new_bit_rate &&
|
|
|
|
|
last_set_fps_ == frame_rate) {
|
2014-06-10 23:48:29 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
2014-09-15 17:52:42 +00:00
|
|
|
if (new_bit_rate > 0) {
|
|
|
|
|
last_set_bitrate_kbps_ = new_bit_rate;
|
|
|
|
|
}
|
|
|
|
|
if (frame_rate > 0) {
|
|
|
|
|
last_set_fps_ = frame_rate;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
bool ret = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
|
|
|
|
|
j_set_rates_method_,
|
2014-09-15 17:52:42 +00:00
|
|
|
last_set_bitrate_kbps_,
|
|
|
|
|
last_set_fps_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-02-13 03:56:14 +00:00
|
|
|
if (!ret) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MediaCodecVideoEncoder::GetOutputBufferInfoIndex(
|
|
|
|
|
JNIEnv* jni,
|
|
|
|
|
jobject j_output_buffer_info) {
|
|
|
|
|
return GetIntField(jni, j_output_buffer_info, j_info_index_field_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jobject MediaCodecVideoEncoder::GetOutputBufferInfoBuffer(
|
|
|
|
|
JNIEnv* jni,
|
|
|
|
|
jobject j_output_buffer_info) {
|
|
|
|
|
return GetObjectField(jni, j_output_buffer_info, j_info_buffer_field_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MediaCodecVideoEncoder::GetOutputBufferInfoIsKeyFrame(
|
|
|
|
|
JNIEnv* jni,
|
|
|
|
|
jobject j_output_buffer_info) {
|
|
|
|
|
return GetBooleanField(jni, j_output_buffer_info, j_info_is_key_frame_field_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
jlong MediaCodecVideoEncoder::GetOutputBufferInfoPresentationTimestampUs(
|
|
|
|
|
JNIEnv* jni,
|
|
|
|
|
jobject j_output_buffer_info) {
|
|
|
|
|
return GetLongField(
|
|
|
|
|
jni, j_output_buffer_info, j_info_presentation_timestamp_us_field_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) {
|
|
|
|
|
while (true) {
|
|
|
|
|
jobject j_output_buffer_info = jni->CallObjectMethod(
|
|
|
|
|
*j_media_codec_video_encoder_, j_dequeue_output_buffer_method_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-09-15 17:52:42 +00:00
|
|
|
if (IsNull(jni, j_output_buffer_info)) {
|
2014-02-13 03:56:14 +00:00
|
|
|
break;
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
int output_buffer_index =
|
|
|
|
|
GetOutputBufferInfoIndex(jni, j_output_buffer_info);
|
|
|
|
|
if (output_buffer_index == -1) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Get frame timestamps from a queue.
|
|
|
|
|
last_output_timestamp_ms_ =
|
2014-02-13 03:56:14 +00:00
|
|
|
GetOutputBufferInfoPresentationTimestampUs(jni, j_output_buffer_info) /
|
|
|
|
|
1000;
|
2014-09-15 17:52:42 +00:00
|
|
|
int32_t timestamp = timestamps_.front();
|
|
|
|
|
timestamps_.erase(timestamps_.begin());
|
|
|
|
|
int64_t render_time_ms = render_times_ms_.front();
|
|
|
|
|
render_times_ms_.erase(render_times_ms_.begin());
|
|
|
|
|
int64_t frame_encoding_time_ms = GetCurrentTimeMs() -
|
|
|
|
|
frame_rtc_times_ms_.front();
|
|
|
|
|
frame_rtc_times_ms_.erase(frame_rtc_times_ms_.begin());
|
2014-06-13 22:59:08 +00:00
|
|
|
frames_in_queue_--;
|
2014-02-13 03:56:14 +00:00
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Extract payload and key frame flag.
|
2014-02-13 03:56:14 +00:00
|
|
|
int32_t callback_status = 0;
|
2014-09-15 17:52:42 +00:00
|
|
|
jobject j_output_buffer =
|
|
|
|
|
GetOutputBufferInfoBuffer(jni, j_output_buffer_info);
|
|
|
|
|
bool key_frame = GetOutputBufferInfoIsKeyFrame(jni, j_output_buffer_info);
|
|
|
|
|
size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer);
|
|
|
|
|
uint8* payload = reinterpret_cast<uint8_t*>(
|
|
|
|
|
jni->GetDirectBufferAddress(j_output_buffer));
|
|
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
|
|
|
|
|
ALOGV("Encoder got output buffer # %d. Size: %d. TS: %lld. Latency: %lld."
|
|
|
|
|
" EncTime: %lld",
|
|
|
|
|
output_buffer_index, payload_size, last_output_timestamp_ms_,
|
|
|
|
|
last_input_timestamp_ms_ - last_output_timestamp_ms_,
|
|
|
|
|
frame_encoding_time_ms);
|
|
|
|
|
|
|
|
|
|
// Calculate and print encoding statistics - every 3 seconds.
|
|
|
|
|
current_frames_++;
|
|
|
|
|
current_bytes_ += payload_size;
|
|
|
|
|
current_encoding_time_ms_ += frame_encoding_time_ms;
|
|
|
|
|
int statistic_time_ms = GetCurrentTimeMs() - start_time_ms_;
|
|
|
|
|
if (statistic_time_ms >= kMediaCodecStatisticsIntervalMs &&
|
|
|
|
|
current_frames_ > 0) {
|
|
|
|
|
ALOGD("Encoder bitrate: %d, target: %d kbps, fps: %d,"
|
|
|
|
|
" encTime: %d for last %d ms",
|
|
|
|
|
current_bytes_ * 8 / statistic_time_ms,
|
|
|
|
|
last_set_bitrate_kbps_,
|
|
|
|
|
(current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms,
|
|
|
|
|
current_encoding_time_ms_ / current_frames_, statistic_time_ms);
|
|
|
|
|
start_time_ms_ = GetCurrentTimeMs();
|
|
|
|
|
current_frames_ = 0;
|
|
|
|
|
current_bytes_= 0;
|
|
|
|
|
current_encoding_time_ms_ = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Callback - return encoded frame.
|
2014-02-13 03:56:14 +00:00
|
|
|
if (callback_) {
|
|
|
|
|
scoped_ptr<webrtc::EncodedImage> image(
|
|
|
|
|
new webrtc::EncodedImage(payload, payload_size, payload_size));
|
|
|
|
|
image->_encodedWidth = width_;
|
|
|
|
|
image->_encodedHeight = height_;
|
2014-09-15 17:52:42 +00:00
|
|
|
image->_timeStamp = timestamp;
|
|
|
|
|
image->capture_time_ms_ = render_time_ms;
|
2014-02-13 03:56:14 +00:00
|
|
|
image->_frameType = (key_frame ? webrtc::kKeyFrame : webrtc::kDeltaFrame);
|
|
|
|
|
image->_completeFrame = true;
|
|
|
|
|
|
|
|
|
|
webrtc::CodecSpecificInfo info;
|
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
|
info.codecType = kVideoCodecVP8;
|
2014-10-09 17:53:09 +00:00
|
|
|
info.codecSpecific.VP8.pictureId = picture_id_;
|
|
|
|
|
info.codecSpecific.VP8.nonReference = false;
|
|
|
|
|
info.codecSpecific.VP8.simulcastIdx = 0;
|
|
|
|
|
info.codecSpecific.VP8.temporalIdx = webrtc::kNoTemporalIdx;
|
|
|
|
|
info.codecSpecific.VP8.layerSync = false;
|
2014-02-13 03:56:14 +00:00
|
|
|
info.codecSpecific.VP8.tl0PicIdx = webrtc::kNoTl0PicIdx;
|
|
|
|
|
info.codecSpecific.VP8.keyIdx = webrtc::kNoKeyIdx;
|
2014-10-09 17:53:09 +00:00
|
|
|
picture_id_ = (picture_id_ + 1) & 0x7FFF;
|
2014-02-13 03:56:14 +00:00
|
|
|
|
|
|
|
|
// Generate a header describing a single fragment.
|
|
|
|
|
webrtc::RTPFragmentationHeader header;
|
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
|
header.VerifyAndAllocateFragmentationHeader(1);
|
|
|
|
|
header.fragmentationOffset[0] = 0;
|
|
|
|
|
header.fragmentationLength[0] = image->_length;
|
|
|
|
|
header.fragmentationPlType[0] = 0;
|
|
|
|
|
header.fragmentationTimeDiff[0] = 0;
|
|
|
|
|
|
|
|
|
|
callback_status = callback_->Encoded(*image, &info, &header);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Return output buffer back to the encoder.
|
2014-02-13 03:56:14 +00:00
|
|
|
bool success = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
|
|
|
|
|
j_release_output_buffer_method_,
|
|
|
|
|
output_buffer_index);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-02-13 03:56:14 +00:00
|
|
|
if (!success) {
|
|
|
|
|
ResetCodec();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
if (callback_status > 0) {
|
2014-02-13 03:56:14 +00:00
|
|
|
drop_next_input_frame_ = true;
|
|
|
|
|
// Theoretically could handle callback_status<0 here, but unclear what that
|
|
|
|
|
// would mean for us.
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simplest-possible implementation of an encoder factory, churns out
|
|
|
|
|
// MediaCodecVideoEncoders on demand (or errors, if that's not possible).
|
|
|
|
|
class MediaCodecVideoEncoderFactory
|
|
|
|
|
: public cricket::WebRtcVideoEncoderFactory {
|
|
|
|
|
public:
|
|
|
|
|
MediaCodecVideoEncoderFactory();
|
|
|
|
|
virtual ~MediaCodecVideoEncoderFactory();
|
|
|
|
|
|
|
|
|
|
// WebRtcVideoEncoderFactory implementation.
|
|
|
|
|
virtual webrtc::VideoEncoder* CreateVideoEncoder(webrtc::VideoCodecType type)
|
|
|
|
|
OVERRIDE;
|
|
|
|
|
virtual const std::vector<VideoCodec>& codecs() const OVERRIDE;
|
|
|
|
|
virtual void DestroyVideoEncoder(webrtc::VideoEncoder* encoder) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Empty if platform support is lacking, const after ctor returns.
|
|
|
|
|
std::vector<VideoCodec> supported_codecs_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() {
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
jclass j_encoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoEncoder");
|
|
|
|
|
bool is_platform_supported = jni->CallStaticBooleanMethod(
|
|
|
|
|
j_encoder_class,
|
|
|
|
|
GetStaticMethodID(jni, j_encoder_class, "isPlatformSupported", "()Z"));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-02-13 03:56:14 +00:00
|
|
|
if (!is_platform_supported)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Wouldn't it be nice if MediaCodec exposed the maximum capabilities of the
|
|
|
|
|
// encoder? Sure would be. Too bad it doesn't. So we hard-code some
|
|
|
|
|
// reasonable defaults.
|
|
|
|
|
supported_codecs_.push_back(
|
2014-07-22 17:44:53 +00:00
|
|
|
VideoCodec(kVideoCodecVP8, "VP8", 1280, 1280, 30));
|
2014-02-13 03:56:14 +00:00
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
MediaCodecVideoEncoderFactory::~MediaCodecVideoEncoderFactory() {}
|
|
|
|
|
|
|
|
|
|
webrtc::VideoEncoder* MediaCodecVideoEncoderFactory::CreateVideoEncoder(
|
|
|
|
|
webrtc::VideoCodecType type) {
|
|
|
|
|
if (type != kVideoCodecVP8 || supported_codecs_.empty())
|
|
|
|
|
return NULL;
|
|
|
|
|
return new MediaCodecVideoEncoder(AttachCurrentThreadIfNeeded());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::vector<MediaCodecVideoEncoderFactory::VideoCodec>&
|
|
|
|
|
MediaCodecVideoEncoderFactory::codecs() const {
|
|
|
|
|
return supported_codecs_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaCodecVideoEncoderFactory::DestroyVideoEncoder(
|
|
|
|
|
webrtc::VideoEncoder* encoder) {
|
|
|
|
|
delete encoder;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
class MediaCodecVideoDecoder : public webrtc::VideoDecoder,
|
2014-07-29 17:36:52 +00:00
|
|
|
public rtc::MessageHandler {
|
2014-07-22 17:44:53 +00:00
|
|
|
public:
|
|
|
|
|
explicit MediaCodecVideoDecoder(JNIEnv* jni);
|
|
|
|
|
virtual ~MediaCodecVideoDecoder();
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
static int SetAndroidObjects(JNIEnv* jni, jobject render_egl_context);
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
virtual int32_t InitDecode(const VideoCodec* codecSettings,
|
|
|
|
|
int32_t numberOfCores) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
virtual int32_t
|
|
|
|
|
Decode(const EncodedImage& inputImage, bool missingFrames,
|
|
|
|
|
const RTPFragmentationHeader* fragmentation,
|
|
|
|
|
const CodecSpecificInfo* codecSpecificInfo = NULL,
|
|
|
|
|
int64_t renderTimeMs = -1) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
virtual int32_t RegisterDecodeCompleteCallback(
|
|
|
|
|
DecodedImageCallback* callback) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
virtual int32_t Release() OVERRIDE;
|
|
|
|
|
|
|
|
|
|
virtual int32_t Reset() OVERRIDE;
|
2014-07-29 17:36:52 +00:00
|
|
|
// rtc::MessageHandler implementation.
|
|
|
|
|
virtual void OnMessage(rtc::Message* msg) OVERRIDE;
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// CHECK-fail if not running on |codec_thread_|.
|
|
|
|
|
void CheckOnCodecThread();
|
|
|
|
|
|
|
|
|
|
int32_t InitDecodeOnCodecThread();
|
|
|
|
|
int32_t ReleaseOnCodecThread();
|
|
|
|
|
int32_t DecodeOnCodecThread(const EncodedImage& inputImage);
|
2014-09-15 17:52:42 +00:00
|
|
|
// Deliver any outputs pending in the MediaCodec to our |callback_| and return
|
|
|
|
|
// true on success.
|
|
|
|
|
bool DeliverPendingOutputs(JNIEnv* jni, int dequeue_timeout_us);
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
bool key_frame_required_;
|
|
|
|
|
bool inited_;
|
2014-09-15 17:52:42 +00:00
|
|
|
bool use_surface_;
|
2014-10-02 16:58:05 +00:00
|
|
|
int error_count_;
|
2014-07-22 17:44:53 +00:00
|
|
|
VideoCodec codec_;
|
|
|
|
|
I420VideoFrame decoded_image_;
|
2014-09-15 17:52:42 +00:00
|
|
|
NativeHandleImpl native_handle_;
|
2014-07-22 17:44:53 +00:00
|
|
|
DecodedImageCallback* callback_;
|
2014-09-15 17:52:42 +00:00
|
|
|
int frames_received_; // Number of frames received by decoder.
|
|
|
|
|
int frames_decoded_; // Number of frames decoded by decoder
|
|
|
|
|
int64_t start_time_ms_; // Start time for statistics.
|
|
|
|
|
int current_frames_; // Number of frames in the current statistics interval.
|
|
|
|
|
int current_bytes_; // Encoded bytes in the current statistics interval.
|
|
|
|
|
int current_decoding_time_ms_; // Overall decoding time in the current second
|
|
|
|
|
uint32_t max_pending_frames_; // Maximum number of pending input frames
|
|
|
|
|
std::vector<int32_t> timestamps_;
|
|
|
|
|
std::vector<int64_t> ntp_times_ms_;
|
|
|
|
|
std::vector<int64_t> frame_rtc_times_ms_; // Time when video frame is sent to
|
|
|
|
|
// decoder input.
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
// State that is constant for the lifetime of this object once the ctor
|
|
|
|
|
// returns.
|
|
|
|
|
scoped_ptr<Thread> codec_thread_; // Thread on which to operate MediaCodec.
|
|
|
|
|
ScopedGlobalRef<jclass> j_media_codec_video_decoder_class_;
|
|
|
|
|
ScopedGlobalRef<jobject> j_media_codec_video_decoder_;
|
|
|
|
|
jmethodID j_init_decode_method_;
|
|
|
|
|
jmethodID j_release_method_;
|
|
|
|
|
jmethodID j_dequeue_input_buffer_method_;
|
|
|
|
|
jmethodID j_queue_input_buffer_method_;
|
|
|
|
|
jmethodID j_dequeue_output_buffer_method_;
|
|
|
|
|
jmethodID j_release_output_buffer_method_;
|
2014-09-15 17:52:42 +00:00
|
|
|
// MediaCodecVideoDecoder fields.
|
2014-07-22 17:44:53 +00:00
|
|
|
jfieldID j_input_buffers_field_;
|
|
|
|
|
jfieldID j_output_buffers_field_;
|
|
|
|
|
jfieldID j_color_format_field_;
|
|
|
|
|
jfieldID j_width_field_;
|
|
|
|
|
jfieldID j_height_field_;
|
|
|
|
|
jfieldID j_stride_field_;
|
|
|
|
|
jfieldID j_slice_height_field_;
|
2014-09-15 17:52:42 +00:00
|
|
|
jfieldID j_surface_texture_field_;
|
|
|
|
|
jfieldID j_textureID_field_;
|
|
|
|
|
// MediaCodecVideoDecoder.DecoderOutputBufferInfo fields.
|
|
|
|
|
jfieldID j_info_index_field_;
|
|
|
|
|
jfieldID j_info_offset_field_;
|
|
|
|
|
jfieldID j_info_size_field_;
|
|
|
|
|
jfieldID j_info_presentation_timestamp_us_field_;
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
// Global references; must be deleted in Release().
|
|
|
|
|
std::vector<jobject> input_buffers_;
|
2014-09-15 17:52:42 +00:00
|
|
|
jobject surface_texture_;
|
2014-10-09 00:00:11 +00:00
|
|
|
jobject previous_surface_texture_;
|
2014-09-15 17:52:42 +00:00
|
|
|
|
|
|
|
|
// Render EGL context.
|
|
|
|
|
static jobject render_egl_context_;
|
2014-07-22 17:44:53 +00:00
|
|
|
};
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
jobject MediaCodecVideoDecoder::render_egl_context_ = NULL;
|
|
|
|
|
|
|
|
|
|
int MediaCodecVideoDecoder::SetAndroidObjects(JNIEnv* jni,
|
|
|
|
|
jobject render_egl_context) {
|
|
|
|
|
if (render_egl_context_) {
|
|
|
|
|
jni->DeleteGlobalRef(render_egl_context_);
|
|
|
|
|
}
|
2014-09-23 21:42:15 +00:00
|
|
|
if (IsNull(jni, render_egl_context)) {
|
|
|
|
|
render_egl_context_ = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
render_egl_context_ = jni->NewGlobalRef(render_egl_context);
|
2014-09-29 23:07:08 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error calling NewGlobalRef for EGL Context.";
|
|
|
|
|
jclass j_egl_context_class = FindClass(jni, "android/opengl/EGLContext");
|
|
|
|
|
if (!jni->IsInstanceOf(render_egl_context_, j_egl_context_class)) {
|
|
|
|
|
ALOGE("Wrong EGL Context.");
|
|
|
|
|
jni->DeleteGlobalRef(render_egl_context_);
|
|
|
|
|
render_egl_context_ = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (render_egl_context_ == NULL) {
|
|
|
|
|
ALOGD("NULL VideoDecoder EGL context - HW surface decoding is disabled.");
|
2014-09-23 21:42:15 +00:00
|
|
|
}
|
2014-09-15 17:52:42 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-18 23:01:03 +00:00
|
|
|
MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni)
|
|
|
|
|
: key_frame_required_(true),
|
|
|
|
|
inited_(false),
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_(0),
|
2014-10-09 00:00:11 +00:00
|
|
|
surface_texture_(NULL),
|
|
|
|
|
previous_surface_texture_(NULL),
|
2014-09-18 23:01:03 +00:00
|
|
|
codec_thread_(new Thread()),
|
|
|
|
|
j_media_codec_video_decoder_class_(
|
|
|
|
|
jni,
|
|
|
|
|
FindClass(jni, "org/webrtc/MediaCodecVideoDecoder")),
|
|
|
|
|
j_media_codec_video_decoder_(
|
|
|
|
|
jni,
|
|
|
|
|
jni->NewObject(*j_media_codec_video_decoder_class_,
|
|
|
|
|
GetMethodID(jni,
|
|
|
|
|
*j_media_codec_video_decoder_class_,
|
|
|
|
|
"<init>",
|
|
|
|
|
"()V"))) {
|
2014-07-22 17:44:53 +00:00
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
codec_thread_->SetName("MediaCodecVideoDecoder", NULL);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(codec_thread_->Start()) << "Failed to start MediaCodecVideoDecoder";
|
2014-07-22 17:44:53 +00:00
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
j_init_decode_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "initDecode",
|
2014-10-02 16:58:05 +00:00
|
|
|
"(IIZZLandroid/opengl/EGLContext;)Z");
|
2014-07-22 17:44:53 +00:00
|
|
|
j_release_method_ =
|
|
|
|
|
GetMethodID(jni, *j_media_codec_video_decoder_class_, "release", "()V");
|
|
|
|
|
j_dequeue_input_buffer_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "dequeueInputBuffer", "()I");
|
|
|
|
|
j_queue_input_buffer_method_ = GetMethodID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "queueInputBuffer", "(IIJ)Z");
|
|
|
|
|
j_dequeue_output_buffer_method_ = GetMethodID(
|
2014-09-15 17:52:42 +00:00
|
|
|
jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer",
|
|
|
|
|
"(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;");
|
2014-07-22 17:44:53 +00:00
|
|
|
j_release_output_buffer_method_ = GetMethodID(
|
2014-09-15 17:52:42 +00:00
|
|
|
jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(IZ)Z");
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
j_input_buffers_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_,
|
|
|
|
|
"inputBuffers", "[Ljava/nio/ByteBuffer;");
|
|
|
|
|
j_output_buffers_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_,
|
|
|
|
|
"outputBuffers", "[Ljava/nio/ByteBuffer;");
|
|
|
|
|
j_color_format_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "colorFormat", "I");
|
|
|
|
|
j_width_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "width", "I");
|
|
|
|
|
j_height_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "height", "I");
|
|
|
|
|
j_stride_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "stride", "I");
|
|
|
|
|
j_slice_height_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "sliceHeight", "I");
|
2014-09-15 17:52:42 +00:00
|
|
|
j_textureID_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "textureID", "I");
|
|
|
|
|
j_surface_texture_field_ = GetFieldID(
|
|
|
|
|
jni, *j_media_codec_video_decoder_class_, "surfaceTexture",
|
|
|
|
|
"Landroid/graphics/SurfaceTexture;");
|
|
|
|
|
|
|
|
|
|
jclass j_decoder_output_buffer_info_class = FindClass(jni,
|
|
|
|
|
"org/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo");
|
|
|
|
|
j_info_index_field_ = GetFieldID(
|
|
|
|
|
jni, j_decoder_output_buffer_info_class, "index", "I");
|
|
|
|
|
j_info_offset_field_ = GetFieldID(
|
|
|
|
|
jni, j_decoder_output_buffer_info_class, "offset", "I");
|
|
|
|
|
j_info_size_field_ = GetFieldID(
|
|
|
|
|
jni, j_decoder_output_buffer_info_class, "size", "I");
|
|
|
|
|
j_info_presentation_timestamp_us_field_ = GetFieldID(
|
|
|
|
|
jni, j_decoder_output_buffer_info_class, "presentationTimestampUs", "J");
|
2014-07-22 17:44:53 +00:00
|
|
|
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "MediaCodecVideoDecoder ctor failed";
|
2014-09-23 21:42:15 +00:00
|
|
|
use_surface_ = true;
|
|
|
|
|
if (render_egl_context_ == NULL)
|
|
|
|
|
use_surface_ = false;
|
2014-07-22 17:44:53 +00:00
|
|
|
memset(&codec_, 0, sizeof(codec_));
|
2014-10-07 17:11:36 +00:00
|
|
|
AllowBlockingCalls();
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
|
2014-09-18 23:01:03 +00:00
|
|
|
// Call Release() to ensure no more callbacks to us after we are deleted.
|
2014-07-22 17:44:53 +00:00
|
|
|
Release();
|
2014-10-09 00:00:11 +00:00
|
|
|
// Delete global references.
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
if (previous_surface_texture_ != NULL)
|
|
|
|
|
jni->DeleteGlobalRef(previous_surface_texture_);
|
|
|
|
|
if (surface_texture_ != NULL)
|
|
|
|
|
jni->DeleteGlobalRef(surface_texture_);
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::InitDecode(const VideoCodec* inst,
|
|
|
|
|
int32_t numberOfCores) {
|
|
|
|
|
if (inst == NULL) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
|
|
|
|
}
|
|
|
|
|
int ret_val = Release();
|
|
|
|
|
if (ret_val < 0) {
|
|
|
|
|
return ret_val;
|
|
|
|
|
}
|
|
|
|
|
// Save VideoCodec instance for later.
|
|
|
|
|
if (&codec_ != inst) {
|
|
|
|
|
codec_ = *inst;
|
|
|
|
|
}
|
|
|
|
|
codec_.maxFramerate = (codec_.maxFramerate >= 1) ? codec_.maxFramerate : 1;
|
|
|
|
|
|
|
|
|
|
// Always start with a complete key frame.
|
|
|
|
|
key_frame_required_ = true;
|
|
|
|
|
frames_received_ = 0;
|
2014-09-15 17:52:42 +00:00
|
|
|
frames_decoded_ = 0;
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
// Call Java init.
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoDecoder::InitDecodeOnCodecThread, this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::InitDecodeOnCodecThread() {
|
|
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
2014-10-02 16:58:05 +00:00
|
|
|
ALOGD("InitDecodeOnCodecThread: %d x %d. Fps: %d. Errors: %d",
|
|
|
|
|
codec_.width, codec_.height, codec_.maxFramerate, error_count_);
|
|
|
|
|
bool use_sw_codec = false;
|
|
|
|
|
if (error_count_ > 1) {
|
|
|
|
|
// If more than one critical errors happen for HW codec, switch to SW codec.
|
|
|
|
|
use_sw_codec = true;
|
|
|
|
|
}
|
2014-07-22 17:44:53 +00:00
|
|
|
|
|
|
|
|
bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_,
|
|
|
|
|
j_init_decode_method_,
|
|
|
|
|
codec_.width,
|
2014-09-15 17:52:42 +00:00
|
|
|
codec_.height,
|
2014-10-02 16:58:05 +00:00
|
|
|
use_sw_codec,
|
2014-09-15 17:52:42 +00:00
|
|
|
use_surface_,
|
|
|
|
|
render_egl_context_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-09-15 17:52:42 +00:00
|
|
|
if (!success) {
|
2014-07-22 17:44:53 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
2014-07-22 17:44:53 +00:00
|
|
|
inited_ = true;
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
max_pending_frames_ = 0;
|
|
|
|
|
if (use_surface_) {
|
|
|
|
|
max_pending_frames_ = 1;
|
|
|
|
|
}
|
|
|
|
|
start_time_ms_ = GetCurrentTimeMs();
|
|
|
|
|
current_frames_ = 0;
|
|
|
|
|
current_bytes_ = 0;
|
|
|
|
|
current_decoding_time_ms_ = 0;
|
|
|
|
|
timestamps_.clear();
|
|
|
|
|
ntp_times_ms_.clear();
|
|
|
|
|
frame_rtc_times_ms_.clear();
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
jobjectArray input_buffers = (jobjectArray)GetObjectField(
|
|
|
|
|
jni, *j_media_codec_video_decoder_, j_input_buffers_field_);
|
|
|
|
|
size_t num_input_buffers = jni->GetArrayLength(input_buffers);
|
|
|
|
|
input_buffers_.resize(num_input_buffers);
|
|
|
|
|
for (size_t i = 0; i < num_input_buffers; ++i) {
|
|
|
|
|
input_buffers_[i] =
|
|
|
|
|
jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
2014-09-15 17:52:42 +00:00
|
|
|
|
|
|
|
|
if (use_surface_) {
|
|
|
|
|
jobject surface_texture = GetObjectField(
|
|
|
|
|
jni, *j_media_codec_video_decoder_, j_surface_texture_field_);
|
2014-10-09 00:00:11 +00:00
|
|
|
if (previous_surface_texture_ != NULL) {
|
|
|
|
|
jni->DeleteGlobalRef(previous_surface_texture_);
|
|
|
|
|
}
|
|
|
|
|
previous_surface_texture_ = surface_texture_;
|
2014-09-15 17:52:42 +00:00
|
|
|
surface_texture_ = jni->NewGlobalRef(surface_texture);
|
|
|
|
|
}
|
|
|
|
|
codec_thread_->PostDelayed(kMediaCodecPollMs, this);
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::Release() {
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(
|
|
|
|
|
Bind(&MediaCodecVideoDecoder::ReleaseOnCodecThread, this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::ReleaseOnCodecThread() {
|
2014-09-15 17:52:42 +00:00
|
|
|
if (!inited_) {
|
2014-07-22 17:44:53 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
2014-07-22 17:44:53 +00:00
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ALOGD("DecoderRelease: Frames received: %d.", frames_received_);
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
2014-09-15 17:52:42 +00:00
|
|
|
for (size_t i = 0; i < input_buffers_.size(); i++) {
|
2014-07-22 17:44:53 +00:00
|
|
|
jni->DeleteGlobalRef(input_buffers_[i]);
|
2014-09-15 17:52:42 +00:00
|
|
|
}
|
2014-07-22 17:44:53 +00:00
|
|
|
input_buffers_.clear();
|
|
|
|
|
jni->CallVoidMethod(*j_media_codec_video_decoder_, j_release_method_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-09-18 23:01:03 +00:00
|
|
|
rtc::MessageQueueManager::Clear(this);
|
2014-07-22 17:44:53 +00:00
|
|
|
inited_ = false;
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MediaCodecVideoDecoder::CheckOnCodecThread() {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread())
|
|
|
|
|
<< "Running on wrong thread!";
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::Decode(
|
|
|
|
|
const EncodedImage& inputImage,
|
|
|
|
|
bool missingFrames,
|
|
|
|
|
const RTPFragmentationHeader* fragmentation,
|
|
|
|
|
const CodecSpecificInfo* codecSpecificInfo,
|
|
|
|
|
int64_t renderTimeMs) {
|
|
|
|
|
if (!inited_) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
if (callback_ == NULL) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
if (inputImage._buffer == NULL && inputImage._length > 0) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
|
|
|
|
|
}
|
|
|
|
|
// Check if encoded frame dimension has changed.
|
|
|
|
|
if ((inputImage._encodedWidth * inputImage._encodedHeight > 0) &&
|
|
|
|
|
(inputImage._encodedWidth != codec_.width ||
|
|
|
|
|
inputImage._encodedHeight != codec_.height)) {
|
|
|
|
|
codec_.width = inputImage._encodedWidth;
|
|
|
|
|
codec_.height = inputImage._encodedHeight;
|
|
|
|
|
InitDecode(&codec_, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Always start with a complete key frame.
|
|
|
|
|
if (key_frame_required_) {
|
|
|
|
|
if (inputImage._frameType != webrtc::kKeyFrame) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
if (!inputImage._completeFrame) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
key_frame_required_ = false;
|
|
|
|
|
}
|
|
|
|
|
if (inputImage._length == 0) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return codec_thread_->Invoke<int32_t>(Bind(
|
|
|
|
|
&MediaCodecVideoDecoder::DecodeOnCodecThread, this, inputImage));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::DecodeOnCodecThread(
|
|
|
|
|
const EncodedImage& inputImage) {
|
|
|
|
|
static uint8_t yVal_ = 0x7f;
|
|
|
|
|
|
|
|
|
|
CheckOnCodecThread();
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Try to drain the decoder and wait until output is not too
|
|
|
|
|
// much behind the input.
|
|
|
|
|
if (frames_received_ > frames_decoded_ + max_pending_frames_) {
|
|
|
|
|
ALOGV("Wait for output...");
|
|
|
|
|
if (!DeliverPendingOutputs(jni, kMediaCodecTimeoutMs * 1000)) {
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-09-15 17:52:42 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
if (frames_received_ > frames_decoded_ + max_pending_frames_) {
|
|
|
|
|
ALOGE("Output buffer dequeue timeout");
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-09-15 17:52:42 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
// Get input buffer.
|
|
|
|
|
int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_decoder_,
|
|
|
|
|
j_dequeue_input_buffer_method_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
if (j_input_buffer_index < 0) {
|
|
|
|
|
ALOGE("dequeueInputBuffer error");
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-07-22 17:44:53 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy encoded data to Java ByteBuffer.
|
|
|
|
|
jobject j_input_buffer = input_buffers_[j_input_buffer_index];
|
|
|
|
|
uint8* buffer =
|
|
|
|
|
reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(buffer) << "Indirect buffer??";
|
2014-07-22 17:44:53 +00:00
|
|
|
int64 buffer_capacity = jni->GetDirectBufferCapacity(j_input_buffer);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
if (buffer_capacity < inputImage._length) {
|
|
|
|
|
ALOGE("Input frame size %d is bigger than buffer size %d.",
|
|
|
|
|
inputImage._length, buffer_capacity);
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-07-22 17:44:53 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
2014-09-15 17:52:42 +00:00
|
|
|
ALOGV("Decoder frame in # %d. Buffer # %d. Size: %d",
|
2014-07-22 17:44:53 +00:00
|
|
|
frames_received_, j_input_buffer_index, inputImage._length);
|
|
|
|
|
memcpy(buffer, inputImage._buffer, inputImage._length);
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Save input image timestamps for later output.
|
|
|
|
|
frames_received_++;
|
|
|
|
|
current_bytes_ += inputImage._length;
|
|
|
|
|
timestamps_.push_back(inputImage._timeStamp);
|
|
|
|
|
ntp_times_ms_.push_back(inputImage.ntp_time_ms_);
|
|
|
|
|
frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
// Feed input to decoder.
|
|
|
|
|
jlong timestamp_us = (frames_received_ * 1000000) / codec_.maxFramerate;
|
|
|
|
|
bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_,
|
|
|
|
|
j_queue_input_buffer_method_,
|
|
|
|
|
j_input_buffer_index,
|
|
|
|
|
inputImage._length,
|
|
|
|
|
timestamp_us);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
if (!success) {
|
|
|
|
|
ALOGE("queueInputBuffer error");
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-07-22 17:44:53 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
// Try to drain the decoder
|
|
|
|
|
if (!DeliverPendingOutputs(jni, 0)) {
|
|
|
|
|
ALOGE("DeliverPendingOutputs error");
|
2014-10-02 16:58:05 +00:00
|
|
|
error_count_++;
|
2014-07-22 17:44:53 +00:00
|
|
|
Reset();
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MediaCodecVideoDecoder::DeliverPendingOutputs(
|
|
|
|
|
JNIEnv* jni, int dequeue_timeout_us) {
|
|
|
|
|
if (frames_received_ <= frames_decoded_) {
|
|
|
|
|
// No need to query for output buffers - decoder is drained.
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Get decoder output.
|
|
|
|
|
jobject j_decoder_output_buffer_info = jni->CallObjectMethod(
|
|
|
|
|
*j_media_codec_video_decoder_,
|
|
|
|
|
j_dequeue_output_buffer_method_,
|
|
|
|
|
dequeue_timeout_us);
|
|
|
|
|
|
|
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
if (IsNull(jni, j_decoder_output_buffer_info)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-19 19:36:13 +00:00
|
|
|
// Extract output buffer info from Java DecoderOutputBufferInfo.
|
2014-09-15 17:52:42 +00:00
|
|
|
int output_buffer_index =
|
|
|
|
|
GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_);
|
|
|
|
|
if (output_buffer_index < 0) {
|
|
|
|
|
ALOGE("dequeueOutputBuffer error : %d", output_buffer_index);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int output_buffer_offset =
|
|
|
|
|
GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_);
|
|
|
|
|
int output_buffer_size =
|
|
|
|
|
GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_);
|
|
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
|
|
|
|
|
// Get decoded video frame properties.
|
2014-07-22 17:44:53 +00:00
|
|
|
int color_format = GetIntField(jni, *j_media_codec_video_decoder_,
|
|
|
|
|
j_color_format_field_);
|
|
|
|
|
int width = GetIntField(jni, *j_media_codec_video_decoder_, j_width_field_);
|
|
|
|
|
int height = GetIntField(jni, *j_media_codec_video_decoder_, j_height_field_);
|
|
|
|
|
int stride = GetIntField(jni, *j_media_codec_video_decoder_, j_stride_field_);
|
|
|
|
|
int slice_height = GetIntField(jni, *j_media_codec_video_decoder_,
|
|
|
|
|
j_slice_height_field_);
|
2014-09-15 17:52:42 +00:00
|
|
|
int texture_id = GetIntField(jni, *j_media_codec_video_decoder_,
|
|
|
|
|
j_textureID_field_);
|
|
|
|
|
|
2014-09-19 19:36:13 +00:00
|
|
|
// Extract data from Java ByteBuffer and create output yuv420 frame -
|
|
|
|
|
// for non surface decoding only.
|
2014-09-15 17:52:42 +00:00
|
|
|
if (!use_surface_) {
|
2014-09-19 19:36:13 +00:00
|
|
|
if (output_buffer_size < width * height * 3 / 2) {
|
|
|
|
|
ALOGE("Insufficient output buffer size: %d", output_buffer_size);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
jobjectArray output_buffers = reinterpret_cast<jobjectArray>(GetObjectField(
|
|
|
|
|
jni, *j_media_codec_video_decoder_, j_output_buffers_field_));
|
|
|
|
|
jobject output_buffer =
|
|
|
|
|
jni->GetObjectArrayElement(output_buffers, output_buffer_index);
|
|
|
|
|
uint8_t* payload = reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(
|
|
|
|
|
output_buffer));
|
|
|
|
|
CHECK_EXCEPTION(jni);
|
|
|
|
|
payload += output_buffer_offset;
|
|
|
|
|
|
|
|
|
|
// Create yuv420 frame.
|
2014-09-15 17:52:42 +00:00
|
|
|
if (color_format == COLOR_FormatYUV420Planar) {
|
|
|
|
|
decoded_image_.CreateFrame(
|
|
|
|
|
stride * slice_height, payload,
|
|
|
|
|
(stride * slice_height) / 4, payload + (stride * slice_height),
|
|
|
|
|
(stride * slice_height) / 4, payload + (5 * stride * slice_height / 4),
|
|
|
|
|
width, height,
|
|
|
|
|
stride, stride / 2, stride / 2);
|
|
|
|
|
} else {
|
|
|
|
|
// All other supported formats are nv12.
|
|
|
|
|
decoded_image_.CreateEmptyFrame(width, height, width,
|
|
|
|
|
width / 2, width / 2);
|
|
|
|
|
libyuv::NV12ToI420(
|
|
|
|
|
payload, stride,
|
|
|
|
|
payload + stride * slice_height, stride,
|
|
|
|
|
decoded_image_.buffer(webrtc::kYPlane),
|
|
|
|
|
decoded_image_.stride(webrtc::kYPlane),
|
|
|
|
|
decoded_image_.buffer(webrtc::kUPlane),
|
|
|
|
|
decoded_image_.stride(webrtc::kUPlane),
|
|
|
|
|
decoded_image_.buffer(webrtc::kVPlane),
|
|
|
|
|
decoded_image_.stride(webrtc::kVPlane),
|
|
|
|
|
width, height);
|
|
|
|
|
}
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
2014-09-19 19:36:13 +00:00
|
|
|
// Get frame timestamps from a queue.
|
|
|
|
|
int32_t timestamp = timestamps_.front();
|
|
|
|
|
timestamps_.erase(timestamps_.begin());
|
|
|
|
|
int64_t ntp_time_ms = ntp_times_ms_.front();
|
|
|
|
|
ntp_times_ms_.erase(ntp_times_ms_.begin());
|
|
|
|
|
int64_t frame_decoding_time_ms = GetCurrentTimeMs() -
|
|
|
|
|
frame_rtc_times_ms_.front();
|
|
|
|
|
frame_rtc_times_ms_.erase(frame_rtc_times_ms_.begin());
|
|
|
|
|
|
|
|
|
|
ALOGV("Decoder frame out # %d. %d x %d. %d x %d. Color: 0x%x. Size: %d."
|
|
|
|
|
" DecTime: %lld", frames_decoded_, width, height, stride, slice_height,
|
|
|
|
|
color_format, output_buffer_size, frame_decoding_time_ms);
|
|
|
|
|
|
2014-07-22 17:44:53 +00:00
|
|
|
// Return output buffer back to codec.
|
2014-09-19 19:36:13 +00:00
|
|
|
bool success = jni->CallBooleanMethod(
|
|
|
|
|
*j_media_codec_video_decoder_,
|
|
|
|
|
j_release_output_buffer_method_,
|
|
|
|
|
output_buffer_index,
|
|
|
|
|
use_surface_);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
if (!success) {
|
|
|
|
|
ALOGE("releaseOutputBuffer error");
|
2014-09-15 17:52:42 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate and print decoding statistics - every 3 seconds.
|
|
|
|
|
frames_decoded_++;
|
|
|
|
|
current_frames_++;
|
|
|
|
|
current_decoding_time_ms_ += frame_decoding_time_ms;
|
|
|
|
|
int statistic_time_ms = GetCurrentTimeMs() - start_time_ms_;
|
|
|
|
|
if (statistic_time_ms >= kMediaCodecStatisticsIntervalMs &&
|
|
|
|
|
current_frames_ > 0) {
|
|
|
|
|
ALOGD("Decoder bitrate: %d kbps, fps: %d, decTime: %d for last %d ms",
|
|
|
|
|
current_bytes_ * 8 / statistic_time_ms,
|
|
|
|
|
(current_frames_ * 1000 + statistic_time_ms / 2) / statistic_time_ms,
|
|
|
|
|
current_decoding_time_ms_ / current_frames_, statistic_time_ms);
|
|
|
|
|
start_time_ms_ = GetCurrentTimeMs();
|
|
|
|
|
current_frames_ = 0;
|
|
|
|
|
current_bytes_= 0;
|
|
|
|
|
current_decoding_time_ms_ = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Callback - output decoded frame.
|
|
|
|
|
int32_t callback_status = WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
if (use_surface_) {
|
|
|
|
|
native_handle_.SetTextureObject(surface_texture_, texture_id);
|
|
|
|
|
TextureVideoFrame texture_image(
|
|
|
|
|
&native_handle_, width, height, timestamp, 0);
|
|
|
|
|
texture_image.set_ntp_time_ms(ntp_time_ms);
|
|
|
|
|
callback_status = callback_->Decoded(texture_image);
|
|
|
|
|
} else {
|
|
|
|
|
decoded_image_.set_timestamp(timestamp);
|
|
|
|
|
decoded_image_.set_ntp_time_ms(ntp_time_ms);
|
|
|
|
|
callback_status = callback_->Decoded(decoded_image_);
|
|
|
|
|
}
|
|
|
|
|
if (callback_status > 0) {
|
|
|
|
|
ALOGE("callback error");
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
2014-09-15 17:52:42 +00:00
|
|
|
return true;
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::RegisterDecodeCompleteCallback(
|
|
|
|
|
DecodedImageCallback* callback) {
|
|
|
|
|
callback_ = callback;
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32_t MediaCodecVideoDecoder::Reset() {
|
|
|
|
|
ALOGD("DecoderReset");
|
|
|
|
|
if (!inited_) {
|
|
|
|
|
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
return InitDecode(&codec_, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-29 17:36:52 +00:00
|
|
|
void MediaCodecVideoDecoder::OnMessage(rtc::Message* msg) {
|
2014-09-15 17:52:42 +00:00
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
if (!inited_) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// We only ever send one message to |this| directly (not through a Bind()'d
|
|
|
|
|
// functor), so expect no ID/data.
|
|
|
|
|
CHECK(!msg->message_id) << "Unexpected message!";
|
|
|
|
|
CHECK(!msg->pdata) << "Unexpected message!";
|
|
|
|
|
CheckOnCodecThread();
|
|
|
|
|
|
2014-10-02 16:58:05 +00:00
|
|
|
if (!DeliverPendingOutputs(jni, 0)) {
|
|
|
|
|
error_count_++;
|
|
|
|
|
Reset();
|
|
|
|
|
}
|
2014-09-15 17:52:42 +00:00
|
|
|
codec_thread_->PostDelayed(kMediaCodecPollMs, this);
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MediaCodecVideoDecoderFactory
|
|
|
|
|
: public cricket::WebRtcVideoDecoderFactory {
|
|
|
|
|
public:
|
|
|
|
|
MediaCodecVideoDecoderFactory();
|
|
|
|
|
virtual ~MediaCodecVideoDecoderFactory();
|
|
|
|
|
// WebRtcVideoDecoderFactory implementation.
|
|
|
|
|
virtual webrtc::VideoDecoder* CreateVideoDecoder(
|
|
|
|
|
webrtc::VideoCodecType type) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
virtual void DestroyVideoDecoder(webrtc::VideoDecoder* decoder) OVERRIDE;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool is_platform_supported_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoDecoderFactory::MediaCodecVideoDecoderFactory() {
|
|
|
|
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
|
|
|
|
ScopedLocalRefFrame local_ref_frame(jni);
|
|
|
|
|
jclass j_decoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoDecoder");
|
|
|
|
|
is_platform_supported_ = jni->CallStaticBooleanMethod(
|
|
|
|
|
j_decoder_class,
|
|
|
|
|
GetStaticMethodID(jni, j_decoder_class, "isPlatformSupported", "()Z"));
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni);
|
2014-07-22 17:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MediaCodecVideoDecoderFactory::~MediaCodecVideoDecoderFactory() {}
|
|
|
|
|
|
|
|
|
|
webrtc::VideoDecoder* MediaCodecVideoDecoderFactory::CreateVideoDecoder(
|
|
|
|
|
webrtc::VideoCodecType type) {
|
|
|
|
|
if (type != kVideoCodecVP8 || !is_platform_supported_) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return new MediaCodecVideoDecoder(AttachCurrentThreadIfNeeded());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaCodecVideoDecoderFactory::DestroyVideoDecoder(
|
|
|
|
|
webrtc::VideoDecoder* decoder) {
|
|
|
|
|
delete decoder;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
// AndroidVideoCapturerJni implements AndroidVideoCapturerDelegate.
|
|
|
|
|
// The purpose of the delegate is to hide the JNI specifics from the C++ only
|
|
|
|
|
// AndroidVideoCapturer.
|
|
|
|
|
// TODO(perkj): Refactor this to a separate file once the jni utility functions
|
|
|
|
|
// and classes have been moved.
|
|
|
|
|
class AndroidVideoCapturerJni : public webrtc::AndroidVideoCapturerDelegate {
|
|
|
|
|
public:
|
|
|
|
|
static int SetAndroidObjects(JNIEnv* jni, jobject appliction_context) {
|
|
|
|
|
if (application_context_) {
|
|
|
|
|
jni->DeleteGlobalRef(application_context_);
|
|
|
|
|
}
|
|
|
|
|
application_context_ = NewGlobalRef(jni, appliction_context);
|
2014-02-13 03:56:14 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
AndroidVideoCapturerJni(JNIEnv* jni, jobject j_video_capturer)
|
|
|
|
|
: j_capturer_global_(jni, j_video_capturer),
|
|
|
|
|
j_video_capturer_class_(
|
|
|
|
|
jni, FindClass(jni, "org/webrtc/VideoCapturerAndroid")),
|
|
|
|
|
j_frame_observer_class_(
|
|
|
|
|
jni,
|
|
|
|
|
FindClass(jni,
|
|
|
|
|
"org/webrtc/VideoCapturerAndroid$NativeFrameObserver")) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Start(int width, int height, int framerate,
|
|
|
|
|
webrtc::AndroidVideoCapturer* capturer) override {
|
|
|
|
|
j_frame_observer_ = NewGlobalRef(
|
|
|
|
|
jni(),
|
|
|
|
|
jni()->NewObject(*j_frame_observer_class_,
|
|
|
|
|
GetMethodID(jni(),
|
|
|
|
|
*j_frame_observer_class_,
|
|
|
|
|
"<init>",
|
|
|
|
|
"(J)V"),
|
|
|
|
|
jlongFromPointer(capturer)));
|
|
|
|
|
CHECK_EXCEPTION(jni()) << "error during NewObject";
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
jmethodID m = GetMethodID(
|
|
|
|
|
jni(), *j_video_capturer_class_, "startCapture",
|
|
|
|
|
"(IIILandroid/content/Context;"
|
|
|
|
|
"Lorg/webrtc/VideoCapturerAndroid$CapturerObserver;)V");
|
|
|
|
|
jni()->CallVoidMethod(*j_capturer_global_,
|
|
|
|
|
m, width, height,
|
|
|
|
|
framerate,
|
|
|
|
|
application_context_,
|
|
|
|
|
j_frame_observer_);
|
|
|
|
|
CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.startCapture";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Stop() override {
|
|
|
|
|
jmethodID m = GetMethodID(jni(), *j_video_capturer_class_,
|
|
|
|
|
"stopCapture", "()Z");
|
|
|
|
|
jboolean result = jni()->CallBooleanMethod(*j_capturer_global_, m);
|
|
|
|
|
CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.stopCapture";
|
|
|
|
|
DeleteGlobalRef(jni(), j_frame_observer_);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GetSupportedFormats() override {
|
|
|
|
|
jmethodID m =
|
|
|
|
|
GetMethodID(jni(), *j_video_capturer_class_,
|
|
|
|
|
"getSupportedFormatsAsJson", "()Ljava/lang/String;");
|
|
|
|
|
jstring j_json_caps =
|
|
|
|
|
(jstring) jni()->CallObjectMethod(*j_capturer_global_, m);
|
|
|
|
|
CHECK_EXCEPTION(jni()) << "error during supportedFormatsAsJson";
|
|
|
|
|
return JavaToStdString(jni(), j_json_caps);
|
|
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
private:
|
|
|
|
|
JNIEnv* jni() { return AttachCurrentThreadIfNeeded(); }
|
2014-02-18 16:57:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
const ScopedGlobalRef<jobject> j_capturer_global_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_video_capturer_class_;
|
|
|
|
|
const ScopedGlobalRef<jclass> j_frame_observer_class_;
|
|
|
|
|
jobject j_frame_observer_;
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
static jobject application_context_;
|
|
|
|
|
};
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
jobject AndroidVideoCapturerJni::application_context_ = nullptr;
|
|
|
|
|
|
|
|
|
|
JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeOnFrameCaptured)
|
|
|
|
|
(JNIEnv* jni, jclass, jlong j_capturer, jbyteArray j_frame,
|
|
|
|
|
jint rotation, jlong ts) {
|
|
|
|
|
jbyte* bytes = jni->GetByteArrayElements(j_frame, NULL);
|
|
|
|
|
reinterpret_cast<webrtc::AndroidVideoCapturer*>(
|
|
|
|
|
j_capturer)->OnIncomingFrame(bytes, jni->GetArrayLength(j_frame),
|
|
|
|
|
rotation, ts);
|
|
|
|
|
jni->ReleaseByteArrayElements(j_frame, bytes, JNI_ABORT);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeCapturerStarted)
|
|
|
|
|
(JNIEnv* jni, jclass, jlong j_capturer, jboolean j_success) {
|
|
|
|
|
reinterpret_cast<webrtc::AndroidVideoCapturer*>(
|
|
|
|
|
j_capturer)->OnCapturerStarted(j_success);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
#endif // #if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
|
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
2013-08-12 23:26:21 +00:00
|
|
|
static DataChannelInterface* ExtractNativeDC(JNIEnv* jni, jobject j_dc) {
|
2013-07-12 16:04:50 +00:00
|
|
|
jfieldID native_dc_id = GetFieldID(jni,
|
|
|
|
|
GetObjectClass(jni, j_dc), "nativeDataChannel", "J");
|
|
|
|
|
jlong j_d = GetLongField(jni, j_dc, native_dc_id);
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<DataChannelInterface*>(j_d);
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, DataChannel_registerObserverNative)(
|
|
|
|
|
JNIEnv* jni, jobject j_dc, jobject j_observer) {
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<DataChannelObserverWrapper> observer(
|
2013-07-12 16:04:50 +00:00
|
|
|
new DataChannelObserverWrapper(jni, j_observer));
|
|
|
|
|
ExtractNativeDC(jni, j_dc)->RegisterObserver(observer.get());
|
2013-12-11 21:07:18 +00:00
|
|
|
return jlongFromPointer(observer.release());
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, DataChannel_unregisterObserverNative)(
|
|
|
|
|
JNIEnv* jni, jobject j_dc, jlong native_observer) {
|
|
|
|
|
ExtractNativeDC(jni, j_dc)->UnregisterObserver();
|
|
|
|
|
delete reinterpret_cast<DataChannelObserverWrapper*>(native_observer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jstring, DataChannel_label)(JNIEnv* jni, jobject j_dc) {
|
|
|
|
|
return JavaStringFromStdString(jni, ExtractNativeDC(jni, j_dc)->label());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, DataChannel_state)(JNIEnv* jni, jobject j_dc) {
|
|
|
|
|
return JavaEnumFromIndex(
|
|
|
|
|
jni, "DataChannel$State", ExtractNativeDC(jni, j_dc)->state());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, DataChannel_bufferedAmount)(JNIEnv* jni, jobject j_dc) {
|
|
|
|
|
uint64 buffered_amount = ExtractNativeDC(jni, j_dc)->buffered_amount();
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_LE(buffered_amount, std::numeric_limits<int64>::max())
|
|
|
|
|
<< "buffered_amount overflowed jlong!";
|
2013-07-12 16:04:50 +00:00
|
|
|
return static_cast<jlong>(buffered_amount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, DataChannel_close)(JNIEnv* jni, jobject j_dc) {
|
|
|
|
|
ExtractNativeDC(jni, j_dc)->Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, DataChannel_sendNative)(JNIEnv* jni, jobject j_dc,
|
|
|
|
|
jbyteArray data, jboolean binary) {
|
|
|
|
|
jbyte* bytes = jni->GetByteArrayElements(data, NULL);
|
|
|
|
|
bool ret = ExtractNativeDC(jni, j_dc)->Send(DataBuffer(
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::Buffer(bytes, jni->GetArrayLength(data)),
|
2013-07-12 16:04:50 +00:00
|
|
|
binary));
|
|
|
|
|
jni->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, DataChannel_dispose)(JNIEnv* jni, jobject j_dc) {
|
2013-08-12 23:26:21 +00:00
|
|
|
CHECK_RELEASE(ExtractNativeDC(jni, j_dc));
|
2013-07-12 16:04:50 +00:00
|
|
|
}
|
|
|
|
|
|
2013-08-06 19:00:53 +00:00
|
|
|
JOW(void, Logging_nativeEnableTracing)(
|
|
|
|
|
JNIEnv* jni, jclass, jstring j_path, jint nativeLevels,
|
|
|
|
|
jint nativeSeverity) {
|
|
|
|
|
std::string path = JavaToStdString(jni, j_path);
|
|
|
|
|
if (nativeLevels != webrtc::kTraceNone) {
|
2013-09-05 16:40:43 +00:00
|
|
|
webrtc::Trace::set_level_filter(nativeLevels);
|
2014-09-19 16:53:46 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2013-10-01 02:40:43 +00:00
|
|
|
if (path != "logcat:") {
|
|
|
|
|
#endif
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EQ(0, webrtc::Trace::SetTraceFile(path.c_str(), false))
|
|
|
|
|
<< "SetTraceFile failed";
|
2014-09-19 16:53:46 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2013-10-01 02:40:43 +00:00
|
|
|
} else {
|
|
|
|
|
// Intentionally leak this to avoid needing to reason about its lifecycle.
|
|
|
|
|
// It keeps no state and functions only as a dispatch point.
|
|
|
|
|
static LogcatTraceContext* g_trace_callback = new LogcatTraceContext();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2013-08-06 19:00:53 +00:00
|
|
|
}
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::LogMessage::LogToDebug(nativeSeverity);
|
2013-08-06 19:00:53 +00:00
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(void, PeerConnection_freePeerConnection)(JNIEnv*, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
CHECK_RELEASE(reinterpret_cast<PeerConnectionInterface*>(j_p));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_freeObserver)(JNIEnv*, jclass, jlong j_p) {
|
|
|
|
|
PCOJava* p = reinterpret_cast<PCOJava*>(j_p);
|
|
|
|
|
delete p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, MediaSource_free)(JNIEnv*, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
CHECK_RELEASE(reinterpret_cast<MediaSourceInterface*>(j_p));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, VideoCapturer_free)(JNIEnv*, jclass, jlong j_p) {
|
|
|
|
|
delete reinterpret_cast<cricket::VideoCapturer*>(j_p);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-17 03:26:59 +00:00
|
|
|
JOW(void, VideoRenderer_freeGuiVideoRenderer)(JNIEnv*, jclass, jlong j_p) {
|
2013-07-10 00:45:36 +00:00
|
|
|
delete reinterpret_cast<VideoRendererWrapper*>(j_p);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-17 03:26:59 +00:00
|
|
|
JOW(void, VideoRenderer_freeWrappedVideoRenderer)(JNIEnv*, jclass, jlong j_p) {
|
|
|
|
|
delete reinterpret_cast<JavaVideoRendererWrapper*>(j_p);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(void, MediaStreamTrack_free)(JNIEnv*, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
CHECK_RELEASE(reinterpret_cast<MediaStreamTrackInterface*>(j_p));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStream_nativeAddAudioTrack)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamInterface*>(pointer)->AddTrack(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStream_nativeAddVideoTrack)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamInterface*>(pointer)
|
|
|
|
|
->AddTrack(reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStream_nativeRemoveAudioTrack)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamInterface*>(pointer)->RemoveTrack(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStream_nativeRemoveVideoTrack)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamInterface*>(pointer)->RemoveTrack(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jstring, MediaStream_nativeLabel)(JNIEnv* jni, jclass, jlong j_p) {
|
|
|
|
|
return JavaStringFromStdString(
|
|
|
|
|
jni, reinterpret_cast<MediaStreamInterface*>(j_p)->label());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, MediaStream_free)(JNIEnv*, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
CHECK_RELEASE(reinterpret_cast<MediaStreamInterface*>(j_p));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateObserver)(
|
|
|
|
|
JNIEnv * jni, jclass, jobject j_observer) {
|
|
|
|
|
return (jlong)new PCOJava(jni, j_observer);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-11 16:58:25 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)(
|
2014-05-14 22:00:50 +00:00
|
|
|
JNIEnv* jni, jclass, jobject context,
|
2014-09-15 17:52:42 +00:00
|
|
|
jboolean initialize_audio, jboolean initialize_video,
|
2014-12-01 20:02:13 +00:00
|
|
|
jboolean vp8_hw_acceleration, jobject render_egl_context) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(g_jvm) << "JNI_OnLoad failed to run?";
|
2013-07-10 00:45:36 +00:00
|
|
|
bool failure = false;
|
2014-12-01 20:02:13 +00:00
|
|
|
vp8_hw_acceleration_enabled = vp8_hw_acceleration;
|
2014-09-15 17:52:42 +00:00
|
|
|
if (!factory_static_initialized) {
|
2014-09-17 11:44:51 +00:00
|
|
|
if (initialize_video) {
|
|
|
|
|
failure |= webrtc::SetRenderAndroidVM(g_jvm);
|
2015-02-11 11:26:56 +00:00
|
|
|
failure |= AndroidVideoCapturerJni::SetAndroidObjects(jni, context);
|
2014-09-17 11:44:51 +00:00
|
|
|
}
|
2014-09-15 17:52:42 +00:00
|
|
|
if (initialize_audio)
|
|
|
|
|
failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
|
|
|
|
|
factory_static_initialized = true;
|
|
|
|
|
}
|
2015-02-11 11:26:56 +00:00
|
|
|
if (initialize_video) {
|
2014-09-15 17:52:42 +00:00
|
|
|
failure |= MediaCodecVideoDecoder::SetAndroidObjects(jni,
|
|
|
|
|
render_egl_context);
|
2015-02-11 11:26:56 +00:00
|
|
|
}
|
2013-07-10 00:45:36 +00:00
|
|
|
return !failure;
|
|
|
|
|
}
|
2014-09-11 16:58:25 +00:00
|
|
|
#endif // defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2013-07-10 00:45:36 +00:00
|
|
|
|
2015-02-09 23:25:58 +00:00
|
|
|
JOW(void, PeerConnectionFactory_initializeFieldTrials)(
|
|
|
|
|
JNIEnv* jni, jclass, jstring j_trials_init_string) {
|
|
|
|
|
field_trials_init_string = NULL;
|
|
|
|
|
if (j_trials_init_string != NULL) {
|
|
|
|
|
const char* init_string =
|
|
|
|
|
jni->GetStringUTFChars(j_trials_init_string, NULL);
|
|
|
|
|
int init_string_length = jni->GetStringUTFLength(j_trials_init_string);
|
|
|
|
|
field_trials_init_string = new char[init_string_length + 1];
|
|
|
|
|
rtc::strcpyn(field_trials_init_string, init_string_length + 1, init_string);
|
|
|
|
|
jni->ReleaseStringUTFChars(j_trials_init_string, init_string);
|
|
|
|
|
LOG(LS_INFO) << "initializeFieldTrials: " << field_trials_init_string ;
|
|
|
|
|
}
|
|
|
|
|
webrtc::field_trial::InitFieldTrialsFromString(field_trials_init_string);
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
// Helper struct for working around the fact that CreatePeerConnectionFactory()
|
|
|
|
|
// comes in two flavors: either entirely automagical (constructing its own
|
|
|
|
|
// threads and deleting them on teardown, but no external codec factory support)
|
|
|
|
|
// or entirely manual (requires caller to delete threads after factory
|
|
|
|
|
// teardown). This struct takes ownership of its ctor's arguments to present a
|
|
|
|
|
// single thing for Java to hold and eventually free.
|
|
|
|
|
class OwnedFactoryAndThreads {
|
|
|
|
|
public:
|
|
|
|
|
OwnedFactoryAndThreads(Thread* worker_thread,
|
|
|
|
|
Thread* signaling_thread,
|
|
|
|
|
PeerConnectionFactoryInterface* factory)
|
|
|
|
|
: worker_thread_(worker_thread),
|
|
|
|
|
signaling_thread_(signaling_thread),
|
|
|
|
|
factory_(factory) {}
|
|
|
|
|
|
|
|
|
|
~OwnedFactoryAndThreads() { CHECK_RELEASE(factory_); }
|
|
|
|
|
|
|
|
|
|
PeerConnectionFactoryInterface* factory() { return factory_; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const scoped_ptr<Thread> worker_thread_;
|
|
|
|
|
const scoped_ptr<Thread> signaling_thread_;
|
|
|
|
|
PeerConnectionFactoryInterface* factory_; // Const after ctor except dtor.
|
|
|
|
|
};
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)(
|
|
|
|
|
JNIEnv* jni, jclass) {
|
2014-05-14 17:33:32 +00:00
|
|
|
// talk/ assumes pretty widely that the current Thread is ThreadManager'd, but
|
|
|
|
|
// ThreadManager only WrapCurrentThread()s the thread where it is first
|
|
|
|
|
// created. Since the semantics around when auto-wrapping happens in
|
2014-07-29 17:36:52 +00:00
|
|
|
// webrtc/base/ are convoluted, we simply wrap here to avoid having to think
|
2014-05-14 17:33:32 +00:00
|
|
|
// about ramifications of auto-wrapping there.
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::ThreadManager::Instance()->WrapCurrentThread();
|
2013-08-12 23:26:21 +00:00
|
|
|
webrtc::Trace::CreateTrace();
|
2014-02-13 03:56:14 +00:00
|
|
|
Thread* worker_thread = new Thread();
|
|
|
|
|
worker_thread->SetName("worker_thread", NULL);
|
|
|
|
|
Thread* signaling_thread = new Thread();
|
|
|
|
|
signaling_thread->SetName("signaling_thread", NULL);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(worker_thread->Start() && signaling_thread->Start())
|
|
|
|
|
<< "Failed to start threads";
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::WebRtcVideoEncoderFactory> encoder_factory;
|
2014-07-22 17:44:53 +00:00
|
|
|
scoped_ptr<cricket::WebRtcVideoDecoderFactory> decoder_factory;
|
2014-09-15 17:52:42 +00:00
|
|
|
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
|
2014-12-01 20:02:13 +00:00
|
|
|
if (vp8_hw_acceleration_enabled) {
|
|
|
|
|
encoder_factory.reset(new MediaCodecVideoEncoderFactory());
|
|
|
|
|
decoder_factory.reset(new MediaCodecVideoDecoderFactory());
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
#endif
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 03:56:14 +00:00
|
|
|
webrtc::CreatePeerConnectionFactory(worker_thread,
|
|
|
|
|
signaling_thread,
|
|
|
|
|
NULL,
|
|
|
|
|
encoder_factory.release(),
|
2014-07-22 17:44:53 +00:00
|
|
|
decoder_factory.release()));
|
2014-02-13 03:56:14 +00:00
|
|
|
OwnedFactoryAndThreads* owned_factory = new OwnedFactoryAndThreads(
|
|
|
|
|
worker_thread, signaling_thread, factory.release());
|
|
|
|
|
return jlongFromPointer(owned_factory);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnectionFactory_freeFactory)(JNIEnv*, jclass, jlong j_p) {
|
2014-02-13 03:56:14 +00:00
|
|
|
delete reinterpret_cast<OwnedFactoryAndThreads*>(j_p);
|
2015-02-09 23:25:58 +00:00
|
|
|
if (field_trials_init_string) {
|
|
|
|
|
webrtc::field_trial::InitFieldTrialsFromString(NULL);
|
|
|
|
|
delete field_trials_init_string;
|
|
|
|
|
field_trials_init_string = NULL;
|
|
|
|
|
}
|
2013-08-12 23:26:21 +00:00
|
|
|
webrtc::Trace::ReturnTrace();
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
2014-02-13 03:56:14 +00:00
|
|
|
static PeerConnectionFactoryInterface* factoryFromJava(jlong j_p) {
|
|
|
|
|
return reinterpret_cast<OwnedFactoryAndThreads*>(j_p)->factory();
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jstring label) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 03:56:14 +00:00
|
|
|
factoryFromJava(native_factory));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<MediaStreamInterface> stream(
|
2013-07-10 00:45:36 +00:00
|
|
|
factory->CreateLocalMediaStream(JavaToStdString(jni, label)));
|
|
|
|
|
return (jlong)stream.release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateVideoSource)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jlong native_capturer,
|
|
|
|
|
jobject j_constraints) {
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<ConstraintsWrapper> constraints(
|
2013-07-10 00:45:36 +00:00
|
|
|
new ConstraintsWrapper(jni, j_constraints));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 03:56:14 +00:00
|
|
|
factoryFromJava(native_factory));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<VideoSourceInterface> source(
|
2013-07-10 00:45:36 +00:00
|
|
|
factory->CreateVideoSource(
|
|
|
|
|
reinterpret_cast<cricket::VideoCapturer*>(native_capturer),
|
|
|
|
|
constraints.get()));
|
|
|
|
|
return (jlong)source.release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateVideoTrack)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jstring id,
|
|
|
|
|
jlong native_source) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 03:56:14 +00:00
|
|
|
factoryFromJava(native_factory));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<VideoTrackInterface> track(
|
2013-07-10 00:45:36 +00:00
|
|
|
factory->CreateVideoTrack(
|
|
|
|
|
JavaToStdString(jni, id),
|
|
|
|
|
reinterpret_cast<VideoSourceInterface*>(native_source)));
|
|
|
|
|
return (jlong)track.release();
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 04:01:04 +00:00
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateAudioSource)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jobject j_constraints) {
|
|
|
|
|
scoped_ptr<ConstraintsWrapper> constraints(
|
|
|
|
|
new ConstraintsWrapper(jni, j_constraints));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 04:01:04 +00:00
|
|
|
factoryFromJava(native_factory));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<AudioSourceInterface> source(
|
2014-02-13 04:01:04 +00:00
|
|
|
factory->CreateAudioSource(constraints.get()));
|
|
|
|
|
return (jlong)source.release();
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreateAudioTrack)(
|
2014-02-13 04:01:04 +00:00
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jstring id,
|
|
|
|
|
jlong native_source) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
2014-02-13 03:56:14 +00:00
|
|
|
factoryFromJava(native_factory));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<AudioTrackInterface> track(factory->CreateAudioTrack(
|
2014-02-13 04:01:04 +00:00
|
|
|
JavaToStdString(jni, id),
|
|
|
|
|
reinterpret_cast<AudioSourceInterface*>(native_source)));
|
2013-07-10 00:45:36 +00:00
|
|
|
return (jlong)track.release();
|
|
|
|
|
}
|
|
|
|
|
|
Makes libjingle_peerconnection_android_unittest run on networkless devices.
PeerConnectionTest.java currently works, but only on a device with
network interfaces up. This is not a problem for desktop, but it is a
problem when running on Android devices since the devices in the lab
generally don't have network (due to the chaotic radio environment in
the device labs, devices are simply kept in flight mode).
The test does work if one modifies this line in the file
webrtc/base/network.cc:
bool ignored = ((cursor->ifa_flags & IFF_LOOPBACK) ||
IsIgnoredNetwork(*network));
If we remove the IFF_LOOPBACK clause, the test starts working on
an Android device in flight mode. This is nice - we're running the
call and packets interact with the OS network stack, which is good
for this end-to-end test. We can't just remove the clause though since
having loopback is undesirable for everyone except the test (right)?
so we need to make this behavior configurable.
This CL takes a stab at a complete solution where we pass a boolean
all the way through the Java PeerConnectionFactory down to the
BasicNetworkManager. This comes as a heavy price in interface
changes though. It's pretty out of proportion, but fundamentally we
need some way of telling the network manager that it is on Android
and in test mode. Passing the boolean all the way through is one way.
Another way might be to put the loopback filter behind an ifdef and
link a custom libjingle_peerconnection.so with the test. That is hacky
but doesn't pollute the interfaces. Not sure how to solve that in GYP
but it could mean some duplication between the production and
test .so files.
It would have been perfect to use flags here, but then we need to
hook up gflags parsing to some main() somewhere to make sure the
flag gets parsed, and make sure to pass that flag in our tests.
I'm not sure how that can be done.
Making the loopback filtering conditional is exactly how we solved the
equivalent problem in content_browsertests in Chrome, and it worked
great.
That's all I could think of.
BUG=4181
R=perkj@webrtc.org, pthatcher@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/36769004
Cr-Commit-Position: refs/heads/master@{#8344}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8344 4adac7df-926f-26a2-2b94-8c16560cd09d
2015-02-12 09:23:59 +00:00
|
|
|
JOW(void, PeerConnectionFactory_nativeSetOptions)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong native_factory, jobject options) {
|
|
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> factory(
|
|
|
|
|
factoryFromJava(native_factory));
|
|
|
|
|
jclass options_class = jni->GetObjectClass(options);
|
|
|
|
|
jfieldID network_ignore_mask_field =
|
|
|
|
|
jni->GetFieldID(options_class, "networkIgnoreMask", "I");
|
|
|
|
|
int network_ignore_mask =
|
|
|
|
|
jni->GetIntField(options, network_ignore_mask_field);
|
|
|
|
|
PeerConnectionFactoryInterface::Options options_to_set;
|
|
|
|
|
|
|
|
|
|
// This doesn't necessarily match the c++ version of this struct; feel free
|
|
|
|
|
// to add more parameters as necessary.
|
|
|
|
|
options_to_set.network_ignore_mask = network_ignore_mask;
|
|
|
|
|
factory->SetOptions(options_to_set);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
static void JavaIceServersToJsepIceServers(
|
|
|
|
|
JNIEnv* jni, jobject j_ice_servers,
|
|
|
|
|
PeerConnectionInterface::IceServers* ice_servers) {
|
|
|
|
|
jclass list_class = GetObjectClass(jni, j_ice_servers);
|
|
|
|
|
jmethodID iterator_id = GetMethodID(
|
|
|
|
|
jni, list_class, "iterator", "()Ljava/util/Iterator;");
|
|
|
|
|
jobject iterator = jni->CallObjectMethod(j_ice_servers, iterator_id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jmethodID iterator_has_next = GetMethodID(
|
|
|
|
|
jni, GetObjectClass(jni, iterator), "hasNext", "()Z");
|
|
|
|
|
jmethodID iterator_next = GetMethodID(
|
|
|
|
|
jni, GetObjectClass(jni, iterator), "next", "()Ljava/lang/Object;");
|
|
|
|
|
while (jni->CallBooleanMethod(iterator, iterator_has_next)) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallBooleanMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jobject j_ice_server = jni->CallObjectMethod(iterator, iterator_next);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
jclass j_ice_server_class = GetObjectClass(jni, j_ice_server);
|
|
|
|
|
jfieldID j_ice_server_uri_id =
|
|
|
|
|
GetFieldID(jni, j_ice_server_class, "uri", "Ljava/lang/String;");
|
|
|
|
|
jfieldID j_ice_server_username_id =
|
|
|
|
|
GetFieldID(jni, j_ice_server_class, "username", "Ljava/lang/String;");
|
|
|
|
|
jfieldID j_ice_server_password_id =
|
|
|
|
|
GetFieldID(jni, j_ice_server_class, "password", "Ljava/lang/String;");
|
|
|
|
|
jstring uri = reinterpret_cast<jstring>(
|
|
|
|
|
GetObjectField(jni, j_ice_server, j_ice_server_uri_id));
|
|
|
|
|
jstring username = reinterpret_cast<jstring>(
|
|
|
|
|
GetObjectField(jni, j_ice_server, j_ice_server_username_id));
|
|
|
|
|
jstring password = reinterpret_cast<jstring>(
|
|
|
|
|
GetObjectField(jni, j_ice_server, j_ice_server_password_id));
|
|
|
|
|
PeerConnectionInterface::IceServer server;
|
|
|
|
|
server.uri = JavaToStdString(jni, uri);
|
|
|
|
|
server.username = JavaToStdString(jni, username);
|
|
|
|
|
server.password = JavaToStdString(jni, password);
|
|
|
|
|
ice_servers->push_back(server);
|
|
|
|
|
}
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallBooleanMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)(
|
|
|
|
|
JNIEnv *jni, jclass, jlong factory, jobject j_ice_servers,
|
|
|
|
|
jobject j_constraints, jlong observer_p) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> f(
|
2014-02-13 03:56:14 +00:00
|
|
|
reinterpret_cast<PeerConnectionFactoryInterface*>(
|
|
|
|
|
factoryFromJava(factory)));
|
2013-07-10 00:45:36 +00:00
|
|
|
PeerConnectionInterface::IceServers servers;
|
|
|
|
|
JavaIceServersToJsepIceServers(jni, j_ice_servers, &servers);
|
|
|
|
|
PCOJava* observer = reinterpret_cast<PCOJava*>(observer_p);
|
|
|
|
|
observer->SetConstraints(new ConstraintsWrapper(jni, j_constraints));
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<PeerConnectionInterface> pc(f->CreatePeerConnection(
|
2014-04-26 00:00:15 +00:00
|
|
|
servers, observer->constraints(), NULL, NULL, observer));
|
2013-07-10 00:45:36 +00:00
|
|
|
return (jlong)pc.release();
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-29 17:36:52 +00:00
|
|
|
static rtc::scoped_refptr<PeerConnectionInterface> ExtractNativePC(
|
2013-07-10 00:45:36 +00:00
|
|
|
JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
jfieldID native_pc_id = GetFieldID(jni,
|
|
|
|
|
GetObjectClass(jni, j_pc), "nativePeerConnection", "J");
|
|
|
|
|
jlong j_p = GetLongField(jni, j_pc, native_pc_id);
|
2014-07-29 17:36:52 +00:00
|
|
|
return rtc::scoped_refptr<PeerConnectionInterface>(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<PeerConnectionInterface*>(j_p));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, PeerConnection_getLocalDescription)(JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
const SessionDescriptionInterface* sdp =
|
|
|
|
|
ExtractNativePC(jni, j_pc)->local_description();
|
|
|
|
|
return sdp ? JavaSdpFromNativeSdp(jni, sdp) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, PeerConnection_getRemoteDescription)(JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
const SessionDescriptionInterface* sdp =
|
|
|
|
|
ExtractNativePC(jni, j_pc)->remote_description();
|
|
|
|
|
return sdp ? JavaSdpFromNativeSdp(jni, sdp) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 16:04:50 +00:00
|
|
|
JOW(jobject, PeerConnection_createDataChannel)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jstring j_label, jobject j_init) {
|
|
|
|
|
DataChannelInit init = JavaDataChannelInitToNative(jni, j_init);
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<DataChannelInterface> channel(
|
2013-07-12 16:04:50 +00:00
|
|
|
ExtractNativePC(jni, j_pc)->CreateDataChannel(
|
|
|
|
|
JavaToStdString(jni, j_label), &init));
|
2013-09-03 18:58:12 +00:00
|
|
|
// Mustn't pass channel.get() directly through NewObject to avoid reading its
|
|
|
|
|
// vararg parameter as 64-bit and reading memory that doesn't belong to the
|
|
|
|
|
// 32-bit parameter.
|
|
|
|
|
jlong nativeChannelPtr = jlongFromPointer(channel.get());
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(nativeChannelPtr) << "Failed to create DataChannel";
|
2013-07-12 16:04:50 +00:00
|
|
|
jclass j_data_channel_class = FindClass(jni, "org/webrtc/DataChannel");
|
|
|
|
|
jmethodID j_data_channel_ctor = GetMethodID(
|
|
|
|
|
jni, j_data_channel_class, "<init>", "(J)V");
|
|
|
|
|
jobject j_channel = jni->NewObject(
|
2013-09-03 18:58:12 +00:00
|
|
|
j_data_channel_class, j_data_channel_ctor, nativeChannelPtr);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during NewObject";
|
2013-07-12 16:04:50 +00:00
|
|
|
// Channel is now owned by Java object, and will be freed from there.
|
2013-08-12 23:26:21 +00:00
|
|
|
int bumped_count = channel->AddRef();
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(bumped_count == 2) << "Unexpected refcount";
|
2013-07-12 16:04:50 +00:00
|
|
|
return j_channel;
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(void, PeerConnection_createOffer)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jobject j_observer, jobject j_constraints) {
|
|
|
|
|
ConstraintsWrapper* constraints =
|
|
|
|
|
new ConstraintsWrapper(jni, j_constraints);
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<CreateSdpObserverWrapper> observer(
|
|
|
|
|
new rtc::RefCountedObject<CreateSdpObserverWrapper>(
|
2013-07-10 00:45:36 +00:00
|
|
|
jni, j_observer, constraints));
|
|
|
|
|
ExtractNativePC(jni, j_pc)->CreateOffer(observer, constraints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_createAnswer)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jobject j_observer, jobject j_constraints) {
|
|
|
|
|
ConstraintsWrapper* constraints =
|
|
|
|
|
new ConstraintsWrapper(jni, j_constraints);
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<CreateSdpObserverWrapper> observer(
|
|
|
|
|
new rtc::RefCountedObject<CreateSdpObserverWrapper>(
|
2013-07-10 00:45:36 +00:00
|
|
|
jni, j_observer, constraints));
|
|
|
|
|
ExtractNativePC(jni, j_pc)->CreateAnswer(observer, constraints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper to create a SessionDescriptionInterface from a SessionDescription.
|
|
|
|
|
static SessionDescriptionInterface* JavaSdpToNativeSdp(
|
|
|
|
|
JNIEnv* jni, jobject j_sdp) {
|
|
|
|
|
jfieldID j_type_id = GetFieldID(
|
|
|
|
|
jni, GetObjectClass(jni, j_sdp), "type",
|
|
|
|
|
"Lorg/webrtc/SessionDescription$Type;");
|
|
|
|
|
jobject j_type = GetObjectField(jni, j_sdp, j_type_id);
|
|
|
|
|
jmethodID j_canonical_form_id = GetMethodID(
|
|
|
|
|
jni, GetObjectClass(jni, j_type), "canonicalForm",
|
|
|
|
|
"()Ljava/lang/String;");
|
|
|
|
|
jstring j_type_string = (jstring)jni->CallObjectMethod(
|
|
|
|
|
j_type, j_canonical_form_id);
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK_EXCEPTION(jni) << "error during CallObjectMethod";
|
2013-07-10 00:45:36 +00:00
|
|
|
std::string std_type = JavaToStdString(jni, j_type_string);
|
|
|
|
|
|
|
|
|
|
jfieldID j_description_id = GetFieldID(
|
|
|
|
|
jni, GetObjectClass(jni, j_sdp), "description", "Ljava/lang/String;");
|
|
|
|
|
jstring j_description = (jstring)GetObjectField(jni, j_sdp, j_description_id);
|
|
|
|
|
std::string std_description = JavaToStdString(jni, j_description);
|
|
|
|
|
|
|
|
|
|
return webrtc::CreateSessionDescription(
|
|
|
|
|
std_type, std_description, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_setLocalDescription)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc,
|
|
|
|
|
jobject j_observer, jobject j_sdp) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<SetSdpObserverWrapper> observer(
|
|
|
|
|
new rtc::RefCountedObject<SetSdpObserverWrapper>(
|
2013-07-10 00:45:36 +00:00
|
|
|
jni, j_observer, reinterpret_cast<ConstraintsWrapper*>(NULL)));
|
|
|
|
|
ExtractNativePC(jni, j_pc)->SetLocalDescription(
|
|
|
|
|
observer, JavaSdpToNativeSdp(jni, j_sdp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_setRemoteDescription)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc,
|
|
|
|
|
jobject j_observer, jobject j_sdp) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<SetSdpObserverWrapper> observer(
|
|
|
|
|
new rtc::RefCountedObject<SetSdpObserverWrapper>(
|
2013-07-10 00:45:36 +00:00
|
|
|
jni, j_observer, reinterpret_cast<ConstraintsWrapper*>(NULL)));
|
|
|
|
|
ExtractNativePC(jni, j_pc)->SetRemoteDescription(
|
|
|
|
|
observer, JavaSdpToNativeSdp(jni, j_sdp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, PeerConnection_updateIce)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jobject j_ice_servers, jobject j_constraints) {
|
|
|
|
|
PeerConnectionInterface::IceServers ice_servers;
|
|
|
|
|
JavaIceServersToJsepIceServers(jni, j_ice_servers, &ice_servers);
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<ConstraintsWrapper> constraints(
|
2013-07-10 00:45:36 +00:00
|
|
|
new ConstraintsWrapper(jni, j_constraints));
|
|
|
|
|
return ExtractNativePC(jni, j_pc)->UpdateIce(ice_servers, constraints.get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, PeerConnection_nativeAddIceCandidate)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jstring j_sdp_mid,
|
|
|
|
|
jint j_sdp_mline_index, jstring j_candidate_sdp) {
|
|
|
|
|
std::string sdp_mid = JavaToStdString(jni, j_sdp_mid);
|
|
|
|
|
std::string sdp = JavaToStdString(jni, j_candidate_sdp);
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<IceCandidateInterface> candidate(
|
2013-07-10 00:45:36 +00:00
|
|
|
webrtc::CreateIceCandidate(sdp_mid, j_sdp_mline_index, sdp, NULL));
|
|
|
|
|
return ExtractNativePC(jni, j_pc)->AddIceCandidate(candidate.get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, PeerConnection_nativeAddLocalStream)(
|
2014-11-04 11:31:29 +00:00
|
|
|
JNIEnv* jni, jobject j_pc, jlong native_stream) {
|
2013-07-10 00:45:36 +00:00
|
|
|
return ExtractNativePC(jni, j_pc)->AddStream(
|
2014-11-04 11:31:29 +00:00
|
|
|
reinterpret_cast<MediaStreamInterface*>(native_stream));
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_nativeRemoveLocalStream)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jlong native_stream) {
|
|
|
|
|
ExtractNativePC(jni, j_pc)->RemoveStream(
|
|
|
|
|
reinterpret_cast<MediaStreamInterface*>(native_stream));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(bool, PeerConnection_nativeGetStats)(
|
|
|
|
|
JNIEnv* jni, jobject j_pc, jobject j_observer, jlong native_track) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<StatsObserverWrapper> observer(
|
|
|
|
|
new rtc::RefCountedObject<StatsObserverWrapper>(jni, j_observer));
|
2013-07-10 00:45:36 +00:00
|
|
|
return ExtractNativePC(jni, j_pc)->GetStats(
|
2014-03-03 21:30:06 +00:00
|
|
|
observer,
|
|
|
|
|
reinterpret_cast<MediaStreamTrackInterface*>(native_track),
|
|
|
|
|
PeerConnectionInterface::kStatsOutputLevelStandard);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, PeerConnection_signalingState)(JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
PeerConnectionInterface::SignalingState state =
|
|
|
|
|
ExtractNativePC(jni, j_pc)->signaling_state();
|
|
|
|
|
return JavaEnumFromIndex(jni, "PeerConnection$SignalingState", state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, PeerConnection_iceConnectionState)(JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
PeerConnectionInterface::IceConnectionState state =
|
|
|
|
|
ExtractNativePC(jni, j_pc)->ice_connection_state();
|
|
|
|
|
return JavaEnumFromIndex(jni, "PeerConnection$IceConnectionState", state);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-21 07:57:06 +00:00
|
|
|
JOW(jobject, PeerConnection_iceGatheringState)(JNIEnv* jni, jobject j_pc) {
|
2013-07-10 00:45:36 +00:00
|
|
|
PeerConnectionInterface::IceGatheringState state =
|
|
|
|
|
ExtractNativePC(jni, j_pc)->ice_gathering_state();
|
2015-01-21 07:57:06 +00:00
|
|
|
return JavaEnumFromIndex(jni, "PeerConnection$IceGatheringState", state);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, PeerConnection_close)(JNIEnv* jni, jobject j_pc) {
|
|
|
|
|
ExtractNativePC(jni, j_pc)->Close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, MediaSource_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
|
2014-07-29 17:36:52 +00:00
|
|
|
rtc::scoped_refptr<MediaSourceInterface> p(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<MediaSourceInterface*>(j_p));
|
|
|
|
|
return JavaEnumFromIndex(jni, "MediaSource$State", p->state());
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-11 11:26:56 +00:00
|
|
|
JOW(jobject, VideoCapturer_nativeCreateVideoCapturer)(
|
2013-07-10 00:45:36 +00:00
|
|
|
JNIEnv* jni, jclass, jstring j_device_name) {
|
2015-02-11 11:26:56 +00:00
|
|
|
// Since we can't create platform specific java implementations in Java, we
|
|
|
|
|
// defer the creation to C land.
|
|
|
|
|
#if defined(ANDROID)
|
|
|
|
|
jclass j_video_capturer_class(
|
|
|
|
|
FindClass(jni, "org/webrtc/VideoCapturerAndroid"));
|
|
|
|
|
const jmethodID j_videocapturer_ctor(GetMethodID(
|
|
|
|
|
jni, j_video_capturer_class, "<init>", "()V"));
|
|
|
|
|
jobject j_video_capturer = jni->NewObject(j_video_capturer_class,
|
|
|
|
|
j_videocapturer_ctor);
|
|
|
|
|
CHECK_EXCEPTION(jni) << "error during NewObject";
|
|
|
|
|
|
|
|
|
|
const jmethodID m(GetMethodID(
|
|
|
|
|
jni, j_video_capturer_class, "Init", "(Ljava/lang/String;)Z"));
|
|
|
|
|
if (!jni->CallBooleanMethod(j_video_capturer, m, j_device_name)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
CHECK_EXCEPTION(jni) << "error during CallVoidMethod";
|
|
|
|
|
|
|
|
|
|
rtc::scoped_ptr<webrtc::AndroidVideoCapturerDelegate> delegate(
|
|
|
|
|
new AndroidVideoCapturerJni(jni, j_video_capturer));
|
|
|
|
|
rtc::scoped_ptr<webrtc::AndroidVideoCapturer> capturer(
|
|
|
|
|
new webrtc::AndroidVideoCapturer(delegate.Pass()));
|
|
|
|
|
|
|
|
|
|
#else
|
2013-07-10 00:45:36 +00:00
|
|
|
std::string device_name = JavaToStdString(jni, j_device_name);
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::DeviceManagerInterface> device_manager(
|
2013-07-10 00:45:36 +00:00
|
|
|
cricket::DeviceManagerFactory::Create());
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(device_manager->Init()) << "DeviceManager::Init() failed";
|
2013-07-10 00:45:36 +00:00
|
|
|
cricket::Device device;
|
|
|
|
|
if (!device_manager->GetVideoCaptureDevice(device_name, &device)) {
|
2013-10-03 18:23:13 +00:00
|
|
|
LOG(LS_ERROR) << "GetVideoCaptureDevice failed for " << device_name;
|
2013-07-10 00:45:36 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::VideoCapturer> capturer(
|
2013-07-10 00:45:36 +00:00
|
|
|
device_manager->CreateVideoCapturer(device));
|
2015-02-11 11:26:56 +00:00
|
|
|
|
|
|
|
|
jclass j_video_capturer_class(
|
|
|
|
|
FindClass(jni, "org/webrtc/VideoCapturer"));
|
|
|
|
|
const jmethodID j_videocapturer_ctor(GetMethodID(
|
|
|
|
|
jni, j_video_capturer_class, "<init>", "()V"));
|
|
|
|
|
jobject j_video_capturer =
|
|
|
|
|
jni->NewObject(j_video_capturer_class,
|
|
|
|
|
j_videocapturer_ctor);
|
|
|
|
|
CHECK_EXCEPTION(jni) << "error during creation of VideoCapturer";
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
const jmethodID j_videocapturer_set_native_capturer(GetMethodID(
|
|
|
|
|
jni, j_video_capturer_class, "setNativeCapturer", "(J)V"));
|
|
|
|
|
jni->CallVoidMethod(j_video_capturer,
|
|
|
|
|
j_videocapturer_set_native_capturer,
|
|
|
|
|
(jlong)capturer.release());
|
|
|
|
|
CHECK_EXCEPTION(jni) << "error during setNativeCapturer";
|
|
|
|
|
return j_video_capturer;
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)(
|
|
|
|
|
JNIEnv* jni, jclass, int x, int y) {
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<VideoRendererWrapper> renderer(VideoRendererWrapper::Create(
|
|
|
|
|
cricket::VideoRendererFactory::CreateGuiVideoRenderer(x, y)));
|
2013-07-10 00:45:36 +00:00
|
|
|
return (jlong)renderer.release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jlong, VideoRenderer_nativeWrapVideoRenderer)(
|
|
|
|
|
JNIEnv* jni, jclass, jobject j_callbacks) {
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<JavaVideoRendererWrapper> renderer(
|
2013-07-10 00:45:36 +00:00
|
|
|
new JavaVideoRendererWrapper(jni, j_callbacks));
|
|
|
|
|
return (jlong)renderer.release();
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-05 17:29:59 +00:00
|
|
|
JOW(void, VideoRenderer_nativeCopyPlane)(
|
|
|
|
|
JNIEnv *jni, jclass, jobject j_src_buffer, jint width, jint height,
|
|
|
|
|
jint src_stride, jobject j_dst_buffer, jint dst_stride) {
|
|
|
|
|
size_t src_size = jni->GetDirectBufferCapacity(j_src_buffer);
|
|
|
|
|
size_t dst_size = jni->GetDirectBufferCapacity(j_dst_buffer);
|
|
|
|
|
CHECK(src_stride >= width) << "Wrong source stride " << src_stride;
|
|
|
|
|
CHECK(dst_stride >= width) << "Wrong destination stride " << dst_stride;
|
|
|
|
|
CHECK(src_size >= src_stride * height)
|
|
|
|
|
<< "Insufficient source buffer capacity " << src_size;
|
|
|
|
|
CHECK(dst_size >= dst_stride * height)
|
|
|
|
|
<< "Isufficient destination buffer capacity " << dst_size;
|
|
|
|
|
uint8_t *src =
|
|
|
|
|
reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_src_buffer));
|
|
|
|
|
uint8_t *dst =
|
|
|
|
|
reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_dst_buffer));
|
|
|
|
|
if (src_stride == dst_stride) {
|
|
|
|
|
memcpy(dst, src, src_stride * height);
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = 0; i < height; i++) {
|
|
|
|
|
memcpy(dst, src, width);
|
|
|
|
|
src += src_stride;
|
|
|
|
|
dst += dst_stride;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-03 18:23:13 +00:00
|
|
|
JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) {
|
|
|
|
|
cricket::VideoCapturer* capturer =
|
|
|
|
|
reinterpret_cast<VideoSourceInterface*>(j_p)->GetVideoCapturer();
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::VideoFormatPod> format(
|
2013-10-03 18:23:13 +00:00
|
|
|
new cricket::VideoFormatPod(*capturer->GetCaptureFormat()));
|
|
|
|
|
capturer->Stop();
|
|
|
|
|
return jlongFromPointer(format.release());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, VideoSource_restart)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong j_p_source, jlong j_p_format) {
|
2014-09-09 11:45:44 +00:00
|
|
|
CHECK(j_p_source);
|
|
|
|
|
CHECK(j_p_format);
|
2014-02-13 03:56:14 +00:00
|
|
|
scoped_ptr<cricket::VideoFormatPod> format(
|
2013-10-03 18:23:13 +00:00
|
|
|
reinterpret_cast<cricket::VideoFormatPod*>(j_p_format));
|
|
|
|
|
reinterpret_cast<VideoSourceInterface*>(j_p_source)->GetVideoCapturer()->
|
|
|
|
|
StartCapturing(cricket::VideoFormat(*format));
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-03 19:04:18 +00:00
|
|
|
JOW(void, VideoSource_freeNativeVideoFormat)(
|
2013-10-03 18:23:13 +00:00
|
|
|
JNIEnv* jni, jclass, jlong j_p) {
|
|
|
|
|
delete reinterpret_cast<cricket::VideoFormatPod*>(j_p);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-10 00:45:36 +00:00
|
|
|
JOW(jstring, MediaStreamTrack_nativeId)(JNIEnv* jni, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return JavaStringFromStdString(
|
|
|
|
|
jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->id());
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jstring, MediaStreamTrack_nativeKind)(JNIEnv* jni, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return JavaStringFromStdString(
|
|
|
|
|
jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->kind());
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStreamTrack_nativeEnabled)(JNIEnv* jni, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)->enabled();
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jobject, MediaStreamTrack_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return JavaEnumFromIndex(
|
|
|
|
|
jni,
|
|
|
|
|
"MediaStreamTrack$State",
|
|
|
|
|
reinterpret_cast<MediaStreamTrackInterface*>(j_p)->state());
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStreamTrack_nativeSetState)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong j_p, jint j_new_state) {
|
|
|
|
|
MediaStreamTrackInterface::TrackState new_state =
|
|
|
|
|
(MediaStreamTrackInterface::TrackState)j_new_state;
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)
|
|
|
|
|
->set_state(new_state);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(jboolean, MediaStreamTrack_nativeSetEnabled)(
|
|
|
|
|
JNIEnv* jni, jclass, jlong j_p, jboolean enabled) {
|
2013-08-12 23:26:21 +00:00
|
|
|
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)
|
|
|
|
|
->set_enabled(enabled);
|
2013-07-10 00:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, VideoTrack_nativeAddRenderer)(
|
|
|
|
|
JNIEnv* jni, jclass,
|
|
|
|
|
jlong j_video_track_pointer, jlong j_renderer_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer)->AddRenderer(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JOW(void, VideoTrack_nativeRemoveRenderer)(
|
|
|
|
|
JNIEnv* jni, jclass,
|
|
|
|
|
jlong j_video_track_pointer, jlong j_renderer_pointer) {
|
2013-08-12 23:26:21 +00:00
|
|
|
reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer)->RemoveRenderer(
|
2013-07-10 00:45:36 +00:00
|
|
|
reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
|
|
|
|
|
}
|