blob: e364b3ad86dee7cb2b8535d31d577f1f0b836903 [file] [log] [blame]
/*
* libjingle
* Copyright 2013, Google Inc.
*
* 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")))
#include <asm/unistd.h>
#include <limits>
#include <map>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include "talk/app/webrtc/mediaconstraintsinterface.h"
#include "talk/app/webrtc/peerconnectioninterface.h"
#include "talk/app/webrtc/videosourceinterface.h"
#include "talk/base/logging.h"
#include "talk/base/ssladapter.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"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/video_engine/include/vie_base.h"
#include "webrtc/voice_engine/include/voe_base.h"
#ifdef ANDROID
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
using webrtc::LogcatTraceContext;
#endif
using icu::UnicodeString;
using webrtc::AudioSourceInterface;
using webrtc::AudioTrackInterface;
using webrtc::AudioTrackVector;
using webrtc::CreateSessionDescriptionObserver;
using webrtc::DataBuffer;
using webrtc::DataChannelInit;
using webrtc::DataChannelInterface;
using webrtc::DataChannelObserver;
using webrtc::IceCandidateInterface;
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;
using webrtc::VideoRendererInterface;
using webrtc::VideoSourceInterface;
using webrtc::VideoTrackInterface;
using webrtc::VideoTrackVector;
// Abort the process if |x| is false, emitting |msg|.
#define CHECK(x, msg) \
if (x) {} else { \
LOG(LS_ERROR) << __FILE__ << ":" << __LINE__ << ": " << msg; \
abort(); \
}
// Abort the process if |jni| has a Java exception pending, emitting |msg|.
#define CHECK_EXCEPTION(jni, msg) \
if (0) {} else { \
if (jni->ExceptionCheck()) { \
jni->ExceptionDescribe(); \
jni->ExceptionClear(); \
CHECK(0, msg); \
} \
}
// Helper that calls ptr->Release() and logs a useful message if that didn't
// actually delete *ptr because of extra refcounts.
#define CHECK_RELEASE(ptr) \
do { \
int count = (ptr)->Release(); \
if (count != 0) { \
LOG(LS_ERROR) << "Refcount unexpectedly not 0: " << (ptr) \
<< ": " << count; \
} \
CHECK(!count, "Unexpected refcount"); \
} while (0)
// Lifted from chromium's base/basictypes.h.
template <bool>
struct CompileAssert {};
#define COMPILE_ASSERT(expr, msg) \
typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
namespace {
static JavaVM* g_jvm = NULL; // Set in JNI_OnLoad().
static pthread_once_t g_jni_ptr_once = PTHREAD_ONCE_INIT;
static pthread_key_t g_jni_ptr; // Key for per-thread JNIEnv* data.
// Return thread ID as a string.
static std::string GetThreadId() {
char buf[21]; // Big enough to hold a kuint64max plus terminating NULL.
CHECK(snprintf(buf, sizeof(buf), "%llu", syscall(__NR_gettid)) <= sizeof(buf),
"Thread id is bigger than uint64??");
return std::string(buf);
}
// Return the current thread's name.
static std::string GetThreadName() {
char name[17];
CHECK(prctl(PR_GET_NAME, name) == 0, "prctl(PR_GET_NAME) failed");
name[16] = '\0';
return std::string(name);
}
static void ThreadDestructor(void* unused) {
jint status = g_jvm->DetachCurrentThread();
CHECK(status == JNI_OK, "Failed to detach thread: " << status);
}
static void CreateJNIPtrKey() {
CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor),
"pthread_key_create");
}
// Deal with difference in signatures between Oracle's jni.h and Android's.
static JNIEnv* AttachCurrentThreadIfNeeded() {
CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey),
"pthread_once");
JNIEnv* jni = reinterpret_cast<JNIEnv*>(pthread_getspecific(g_jni_ptr));
if (jni == NULL) {
#ifdef _JAVASOFT_JNI_H_ // Oracle's jni.h violates the JNI spec!
void* env;
#else
JNIEnv* env;
#endif
char* name = strdup((GetThreadName() + " - " + GetThreadId()).c_str());
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = name;
args.group = NULL;
CHECK(!g_jvm->AttachCurrentThread(&env, &args), "Failed to attach thread");
free(name);
CHECK(env, "AttachCurrentThread handed back NULL!");
jni = reinterpret_cast<JNIEnv*>(env);
CHECK(!pthread_setspecific(g_jni_ptr, jni), "pthread_setspecific");
}
return jni;
}
// Return a |jlong| that will automatically convert back to |ptr| when assigned
// to a |uint64|
static jlong jlongFromPointer(void* ptr) {
COMPILE_ASSERT(sizeof(intptr_t) <= sizeof(uint64),
Time_to_rethink_the_use_of_jlongs);
// Guaranteed to fit by the COMPILE_ASSERT above.
uint64 u64 = reinterpret_cast<intptr_t>(ptr);
// If the unsigned value fits in the signed type, return it directly.
if (u64 <= std::numeric_limits<int64>::max())
return u64;
// Otherwise, we need to get move u64 into the range of [int64min, -1] subject
// to the constraints of remaining equal to |u64| modulo |2^64|.
u64 = std::numeric_limits<uint64>::max() - u64; // In [0,int64max].
int64 i64 = -u64; // In [-int64max, 0].
i64 -= 1; // In [int64min, -1], and i64+2^64==u64.
return i64;
}
// 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");
LoadClass(jni, "org/webrtc/DataChannel");
LoadClass(jni, "org/webrtc/DataChannel$Buffer");
LoadClass(jni, "org/webrtc/DataChannel$Init");
LoadClass(jni, "org/webrtc/DataChannel$State");
LoadClass(jni, "org/webrtc/IceCandidate");
LoadClass(jni, "org/webrtc/MediaSource$State");
LoadClass(jni, "org/webrtc/MediaStream");
LoadClass(jni, "org/webrtc/MediaStreamTrack$State");
LoadClass(jni, "org/webrtc/PeerConnection$SignalingState");
LoadClass(jni, "org/webrtc/PeerConnection$IceConnectionState");
LoadClass(jni, "org/webrtc/PeerConnection$IceGatheringState");
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");
LoadClass(jni, "org/webrtc/VideoTrack");
}
~ClassReferenceHolder() {
CHECK(classes_.empty(), "Must call FreeReferences() before dtor!");
}
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);
CHECK(it != classes_.end(), "Unexpected GetClass() call for: " << name);
return it->second;
}
private:
void LoadClass(JNIEnv* jni, const std::string& name) {
jclass localRef = jni->FindClass(name.c_str());
CHECK_EXCEPTION(jni, "error during FindClass: " << name);
CHECK(localRef, name);
jclass globalRef = reinterpret_cast<jclass>(jni->NewGlobalRef(localRef));
CHECK_EXCEPTION(jni, "error during NewGlobalRef: " << name);
CHECK(globalRef, name);
bool inserted = classes_.insert(std::make_pair(name, globalRef)).second;
CHECK(inserted, "Duplicate class name: " << name);
}
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);
CHECK_EXCEPTION(jni,
"error during GetMethodID: " << name << ", " << signature);
CHECK(m, name << ", " << signature);
return m;
}
jmethodID GetStaticMethodID(
JNIEnv* jni, jclass c, const char* name, const char* signature) {
jmethodID m = jni->GetStaticMethodID(c, name, signature);
CHECK_EXCEPTION(jni,
"error during GetStaticMethodID: "
<< name << ", " << signature);
CHECK(m, name << ", " << signature);
return m;
}
jfieldID GetFieldID(
JNIEnv* jni, jclass c, const char* name, const char* signature) {
jfieldID f = jni->GetFieldID(c, name, signature);
CHECK_EXCEPTION(jni, "error during GetFieldID");
CHECK(f, name << ", " << signature);
return f;
}
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);
CHECK_EXCEPTION(jni, "error during GetObjectClass");
CHECK(c, "");
return c;
}
jobject GetObjectField(JNIEnv* jni, jobject object, jfieldID id) {
jobject o = jni->GetObjectField(object, id);
CHECK_EXCEPTION(jni, "error during GetObjectField");
CHECK(o, "");
return o;
}
jstring GetStringField(JNIEnv* jni, jobject object, jfieldID id) {
return static_cast<jstring>(GetObjectField(jni, object, id));
}
jlong GetLongField(JNIEnv* jni, jobject object, jfieldID id) {
jlong l = jni->GetLongField(object, id);
CHECK_EXCEPTION(jni, "error during GetLongField");
return l;
}
jint GetIntField(JNIEnv* jni, jobject object, jfieldID id) {
jint i = jni->GetIntField(object, id);
CHECK_EXCEPTION(jni, "error during GetIntField");
return i;
}
bool GetBooleanField(JNIEnv* jni, jobject object, jfieldID id) {
jboolean b = jni->GetBooleanField(object, id);
CHECK_EXCEPTION(jni, "error during GetBooleanField");
return b;
}
jobject NewGlobalRef(JNIEnv* jni, jobject o) {
jobject ret = jni->NewGlobalRef(o);
CHECK_EXCEPTION(jni, "error during NewGlobalRef");
CHECK(ret, "");
return ret;
}
void DeleteGlobalRef(JNIEnv* jni, jobject o) {
jni->DeleteGlobalRef(o);
CHECK_EXCEPTION(jni, "error during DeleteGlobalRef");
}
// 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)) {
CHECK_EXCEPTION(jni, "error during NewLocalRef");
}
~WeakRef() {
if (obj_) {
jni_->DeleteLocalRef(obj_);
CHECK_EXCEPTION(jni_, "error during DeleteLocalRef");
}
}
jobject obj() { return obj_; }
private:
JNIEnv* const jni_;
jobject const obj_;
};
// Given a local ref, take ownership of it and delete the ref when this goes out
// of scope.
template<class T> // T is jclass, jobject, jintArray, etc.
class ScopedLocalRef {
public:
ScopedLocalRef(JNIEnv* jni, T obj)
: jni_(jni), obj_(obj) {}
~ScopedLocalRef() {
jni_->DeleteLocalRef(obj_);
}
T operator*() const {
return obj_;
}
private:
JNIEnv* jni_;
T obj_;
};
// Scoped holder for global Java refs.
template<class T> // T is jclass, jobject, jintArray, etc.
class ScopedGlobalRef {
public:
explicit ScopedGlobalRef(JNIEnv* jni, T obj)
: obj_(static_cast<T>(jni->NewGlobalRef(obj))) {}
~ScopedGlobalRef() {
DeleteGlobalRef(AttachCurrentThreadIfNeeded(), obj_);
}
T operator*() const {
return obj_;
}
private:
T obj_;
};
// 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());
ScopedLocalRef<jobjectArray> state_values(
jni,
(jobjectArray)jni->CallStaticObjectMethod(state_class, state_values_id));
CHECK_EXCEPTION(jni, "error during CallStaticObjectMethod");
jobject ret = jni->GetObjectArrayElement(*state_values, index);
CHECK_EXCEPTION(jni, "error during GetObjectArrayElement");
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());
CHECK_EXCEPTION(jni, "error during NewString");
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);
CHECK_EXCEPTION(jni, "Error during GetStringChars");
UnicodeString ustr(jchars, jni->GetStringLength(j_string));
CHECK_EXCEPTION(jni, "Error during GetStringLength");
jni->ReleaseStringChars(j_string, jchars);
CHECK_EXCEPTION(jni, "Error during ReleaseStringChars");
std::string ret;
return ustr.toUTF8String(ret);
}
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;
}
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),
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")),
j_audio_track_ctor_(GetMethodID(
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")) {
}
virtual ~PCOJava() {}
virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE {
std::string sdp;
CHECK(candidate->ToString(&sdp), "got so far: " << sdp);
jclass candidate_class = FindClass(jni(), "org/webrtc/IceCandidate");
jmethodID ctor = GetMethodID(jni(), candidate_class,
"<init>", "(Ljava/lang/String;ILjava/lang/String;)V");
ScopedLocalRef<jstring> j_mid(
jni(), JavaStringFromStdString(jni(), candidate->sdp_mid()));
ScopedLocalRef<jstring> j_sdp(jni(), JavaStringFromStdString(jni(), sdp));
ScopedLocalRef<jobject> j_candidate(jni(), jni()->NewObject(
candidate_class, ctor, *j_mid, candidate->sdp_mline_index(), *j_sdp));
CHECK_EXCEPTION(jni(), "error during NewObject");
jmethodID m = GetMethodID(jni(), *j_observer_class_,
"onIceCandidate", "(Lorg/webrtc/IceCandidate;)V");
jni()->CallVoidMethod(*j_observer_global_, m, *j_candidate);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnError() OVERRIDE {
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onError", "(V)V");
jni()->CallVoidMethod(*j_observer_global_, m);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnSignalingChange(
PeerConnectionInterface::SignalingState new_state) OVERRIDE {
jmethodID m = GetMethodID(
jni(), *j_observer_class_, "onSignalingChange",
"(Lorg/webrtc/PeerConnection$SignalingState;)V");
ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
jni(), "PeerConnection$SignalingState", new_state));
jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnIceConnectionChange(
PeerConnectionInterface::IceConnectionState new_state) OVERRIDE {
jmethodID m = GetMethodID(
jni(), *j_observer_class_, "onIceConnectionChange",
"(Lorg/webrtc/PeerConnection$IceConnectionState;)V");
ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
jni(), "PeerConnection$IceConnectionState", new_state));
jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnIceGatheringChange(
PeerConnectionInterface::IceGatheringState new_state) OVERRIDE {
jmethodID m = GetMethodID(
jni(), *j_observer_class_, "onIceGatheringChange",
"(Lorg/webrtc/PeerConnection$IceGatheringState;)V");
ScopedLocalRef<jobject> new_state_enum(jni(), JavaEnumFromIndex(
jni(), "PeerConnection$IceGatheringState", new_state));
jni()->CallVoidMethod(*j_observer_global_, m, *new_state_enum);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnAddStream(MediaStreamInterface* stream) OVERRIDE {
ScopedLocalRef<jobject> j_stream(jni(), jni()->NewObject(
*j_media_stream_class_, j_media_stream_ctor_, (jlong)stream));
CHECK_EXCEPTION(jni(), "error during NewObject");
AudioTrackVector audio_tracks = stream->GetAudioTracks();
for (size_t i = 0; i < audio_tracks.size(); ++i) {
AudioTrackInterface* track = audio_tracks[i];
ScopedLocalRef<jstring> id(
jni(), JavaStringFromStdString(jni(), track->id()));
ScopedLocalRef<jobject> j_track(jni(), jni()->NewObject(
*j_audio_track_class_, j_audio_track_ctor_, (jlong)track, *id));
CHECK_EXCEPTION(jni(), "error during NewObject");
jfieldID audio_tracks_id = GetFieldID(jni(),
*j_media_stream_class_,
"audioTracks",
"Ljava/util/LinkedList;");
ScopedLocalRef<jobject> audio_tracks(jni(), GetObjectField(
jni(), *j_stream, audio_tracks_id));
jmethodID add = GetMethodID(jni(),
GetObjectClass(jni(), *audio_tracks), "add", "(Ljava/lang/Object;)Z");
jboolean added = jni()->CallBooleanMethod(*audio_tracks, add, *j_track);
CHECK_EXCEPTION(jni(), "error during CallBooleanMethod");
CHECK(added, "");
}
VideoTrackVector video_tracks = stream->GetVideoTracks();
for (size_t i = 0; i < video_tracks.size(); ++i) {
VideoTrackInterface* track = video_tracks[i];
ScopedLocalRef<jstring> id(
jni(), JavaStringFromStdString(jni(), track->id()));
ScopedLocalRef<jobject> j_track(jni(), jni()->NewObject(
*j_video_track_class_, j_video_track_ctor_, (jlong)track, *id));
CHECK_EXCEPTION(jni(), "error during NewObject");
jfieldID video_tracks_id = GetFieldID(jni(),
*j_media_stream_class_,
"videoTracks",
"Ljava/util/LinkedList;");
ScopedLocalRef<jobject> video_tracks(jni(), GetObjectField(
jni(), *j_stream, video_tracks_id));
jmethodID add = GetMethodID(jni(),
GetObjectClass(jni(), *video_tracks), "add", "(Ljava/lang/Object;)Z");
jboolean added = jni()->CallBooleanMethod(*video_tracks, add, *j_track);
CHECK_EXCEPTION(jni(), "error during CallBooleanMethod");
CHECK(added, "");
}
streams_[stream] = jni()->NewWeakGlobalRef(*j_stream);
CHECK_EXCEPTION(jni(), "error during NewWeakGlobalRef");
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onAddStream",
"(Lorg/webrtc/MediaStream;)V");
jni()->CallVoidMethod(*j_observer_global_, m, *j_stream);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnRemoveStream(MediaStreamInterface* stream) OVERRIDE {
NativeToJavaStreamsMap::iterator it = streams_.find(stream);
CHECK(it != streams_.end(), "unexpected stream: " << std::hex << stream);
WeakRef s(jni(), it->second);
streams_.erase(it);
if (!s.obj())
return;
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onRemoveStream",
"(Lorg/webrtc/MediaStream;)V");
jni()->CallVoidMethod(*j_observer_global_, m, s.obj());
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnDataChannel(DataChannelInterface* channel) OVERRIDE {
ScopedLocalRef<jobject> j_channel(jni(), jni()->NewObject(
*j_data_channel_class_, j_data_channel_ctor_, (jlong)channel));
CHECK_EXCEPTION(jni(), "error during NewObject");
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onDataChannel",
"(Lorg/webrtc/DataChannel;)V");
jni()->CallVoidMethod(*j_observer_global_, m, *j_channel);
// 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();
CHECK(bumped_count == 2, "Unexpected refcount OnDataChannel");
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
void SetConstraints(ConstraintsWrapper* constraints) {
CHECK(!constraints_.get(), "constraints already set!");
constraints_.reset(constraints);
}
const ConstraintsWrapper* constraints() { return constraints_.get(); }
private:
JNIEnv* jni() {
return AttachCurrentThreadIfNeeded();
}
const ScopedGlobalRef<jobject> j_observer_global_;
const ScopedGlobalRef<jclass> j_observer_class_;
const ScopedGlobalRef<jclass> j_media_stream_class_;
const jmethodID j_media_stream_ctor_;
const ScopedGlobalRef<jclass> j_audio_track_class_;
const jmethodID j_audio_track_ctor_;
const ScopedGlobalRef<jclass> j_video_track_class_;
const jmethodID j_video_track_ctor_;
const ScopedGlobalRef<jclass> j_data_channel_class_;
const jmethodID j_data_channel_ctor_;
typedef std::map<void*, jweak> NativeToJavaStreamsMap;
NativeToJavaStreamsMap streams_; // C++ -> Java streams.
talk_base::scoped_ptr<ConstraintsWrapper> constraints_;
};
// 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.
virtual const Constraints& GetMandatory() const OVERRIDE {
return mandatory_;
}
virtual const Constraints& GetOptional() const OVERRIDE {
return optional_;
}
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);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
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)) {
CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
jobject entry = jni->CallObjectMethod(j_iterator, j_next);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
jmethodID get_key = GetMethodID(jni,
GetObjectClass(jni, entry), "getKey", "()Ljava/lang/String;");
jstring j_key = reinterpret_cast<jstring>(
jni->CallObjectMethod(entry, get_key));
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
jmethodID get_value = GetMethodID(jni,
GetObjectClass(jni, entry), "getValue", "()Ljava/lang/String;");
jstring j_value = reinterpret_cast<jstring>(
jni->CallObjectMethod(entry, get_value));
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
field->push_back(Constraint(JavaToStdString(jni, j_key),
JavaToStdString(jni, j_value)));
}
CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
}
Constraints mandatory_;
Constraints optional_;
};
static jobject JavaSdpFromNativeSdp(
JNIEnv* jni, const SessionDescriptionInterface* desc) {
std::string sdp;
CHECK(desc->ToString(&sdp), "got so far: " << sdp);
ScopedLocalRef<jstring> j_description(jni, JavaStringFromStdString(jni, sdp));
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;");
ScopedLocalRef<jstring> j_type_string(
jni, JavaStringFromStdString(jni, desc->type()));
jobject j_type = jni->CallStaticObjectMethod(
j_type_class, j_type_from_canonical, *j_type_string);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
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(
j_sdp_class, j_sdp_ctor, j_type, *j_description);
CHECK_EXCEPTION(jni, "error during NewObject");
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),
j_observer_global_(jni, j_observer),
j_observer_class_(jni, GetObjectClass(jni, j_observer)) {
}
virtual ~SdpObserverWrapper() {}
// Can't mark OVERRIDE because of templating.
virtual void OnSuccess() {
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onSetSuccess", "()V");
jni()->CallVoidMethod(*j_observer_global_, m);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
// Can't mark OVERRIDE because of templating.
virtual void OnSuccess(SessionDescriptionInterface* desc) {
jmethodID m = GetMethodID(
jni(), *j_observer_class_, "onCreateSuccess",
"(Lorg/webrtc/SessionDescription;)V");
ScopedLocalRef<jobject> j_sdp(jni(), JavaSdpFromNativeSdp(jni(), desc));
jni()->CallVoidMethod(*j_observer_global_, m, *j_sdp);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
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) {
jmethodID m = GetMethodID(jni(), *j_observer_class_, "on" + op + "Failure",
"(Ljava/lang/String;)V");
ScopedLocalRef<jstring> j_error_string(
jni(), JavaStringFromStdString(jni(), error));
jni()->CallVoidMethod(*j_observer_global_, m, *j_error_string);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
private:
JNIEnv* jni() {
return AttachCurrentThreadIfNeeded();
}
talk_base::scoped_ptr<ConstraintsWrapper> constraints_;
const ScopedGlobalRef<jobject> j_observer_global_;
const ScopedGlobalRef<jclass> j_observer_class_;
};
class CreateSdpObserverWrapper
: public SdpObserverWrapper<CreateSessionDescriptionObserver> {
public:
CreateSdpObserverWrapper(JNIEnv* jni, jobject j_observer,
ConstraintsWrapper* constraints)
: SdpObserverWrapper(jni, j_observer, constraints) {}
virtual void OnFailure(const std::string& error) OVERRIDE {
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) {}
virtual void OnFailure(const std::string& error) OVERRIDE {
SdpObserverWrapper::OnFailure(std::string("Set"), error);
}
};
// 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)),
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_class_(jni, FindClass(jni, "org/webrtc/DataChannel$Buffer")),
j_buffer_ctor_(GetMethodID(jni, *j_buffer_class_,
"<init>", "(Ljava/nio/ByteBuffer;Z)V")) {
}
virtual ~DataChannelObserverWrapper() {}
virtual void OnStateChange() OVERRIDE {
jni()->CallVoidMethod(*j_observer_global_, j_on_state_change_mid_);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnMessage(const DataBuffer& buffer) OVERRIDE {
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);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
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_;
};
// 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)
: j_observer_global_(jni, j_observer),
j_observer_class_(jni, GetObjectClass(jni, j_observer)),
j_stats_report_class_(jni, FindClass(jni, "org/webrtc/StatsReport")),
j_stats_report_ctor_(GetMethodID(
jni, *j_stats_report_class_, "<init>",
"(Ljava/lang/String;Ljava/lang/String;D"
"[Lorg/webrtc/StatsReport$Value;)V")),
j_value_class_(jni, FindClass(
jni, "org/webrtc/StatsReport$Value")),
j_value_ctor_(GetMethodID(
jni, *j_value_class_, "<init>",
"(Ljava/lang/String;Ljava/lang/String;)V")) {
}
virtual ~StatsObserverWrapper() {}
virtual void OnComplete(const std::vector<StatsReport>& reports) OVERRIDE {
ScopedLocalRef<jobjectArray> j_reports(jni(),
ReportsToJava(jni(), reports));
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onComplete",
"([Lorg/webrtc/StatsReport;)V");
jni()->CallVoidMethod(*j_observer_global_, m, *j_reports);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
private:
jobjectArray ReportsToJava(
JNIEnv* jni, const std::vector<StatsReport>& reports) {
jobjectArray reports_array = jni->NewObjectArray(
reports.size(), *j_stats_report_class_, NULL);
for (int i = 0; i < reports.size(); ++i) {
const StatsReport& report = reports[i];
ScopedLocalRef<jstring> j_id(
jni, JavaStringFromStdString(jni, report.id));
ScopedLocalRef<jstring> j_type(
jni, JavaStringFromStdString(jni, report.type));
ScopedLocalRef<jobjectArray> j_values(
jni, ValuesToJava(jni, report.values));
ScopedLocalRef<jobject> j_report(jni, jni->NewObject(
*j_stats_report_class_, j_stats_report_ctor_, *j_id, *j_type,
report.timestamp, *j_values));
jni->SetObjectArrayElement(reports_array, i, *j_report);
}
return reports_array;
}
jobjectArray ValuesToJava(JNIEnv* jni, const StatsReport::Values& values) {
jobjectArray j_values = jni->NewObjectArray(
values.size(), *j_value_class_, NULL);
for (int i = 0; i < values.size(); ++i) {
const StatsReport::Value& value = values[i];
ScopedLocalRef<jstring> j_name(
jni, JavaStringFromStdString(jni, value.name));
ScopedLocalRef<jstring> j_value(
jni, JavaStringFromStdString(jni, value.value));
ScopedLocalRef<jobject> j_element_value(jni, jni->NewObject(
*j_value_class_, j_value_ctor_, *j_name, *j_value));
jni->SetObjectArrayElement(j_values, i, *j_element_value);
}
return j_values;
}
JNIEnv* jni() {
return AttachCurrentThreadIfNeeded();
}
const ScopedGlobalRef<jobject> j_observer_global_;
const ScopedGlobalRef<jclass> j_observer_class_;
const ScopedGlobalRef<jclass> j_stats_report_class_;
const jmethodID j_stats_report_ctor_;
const ScopedGlobalRef<jclass> j_value_class_;
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() {}
virtual void SetSize(int width, int height) OVERRIDE {
const bool kNotReserved = false; // What does this param mean??
renderer_->SetSize(width, height, kNotReserved);
}
virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE {
renderer_->RenderFrame(frame);
}
private:
explicit VideoRendererWrapper(cricket::VideoRenderer* renderer)
: renderer_(renderer) {}
talk_base::scoped_ptr<cricket::VideoRenderer> renderer_;
};
// Wrapper dispatching webrtc::VideoRendererInterface to a Java VideoRenderer
// instance.
class JavaVideoRendererWrapper : public VideoRendererInterface {
public:
JavaVideoRendererWrapper(JNIEnv* jni, jobject j_callbacks)
: 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")),
j_frame_ctor_id_(GetMethodID(
jni, *j_frame_class_, "<init>", "(II[I[Ljava/nio/ByteBuffer;)V")),
j_byte_buffer_class_(jni, FindClass(jni, "java/nio/ByteBuffer")) {
CHECK_EXCEPTION(jni, "");
}
virtual ~JavaVideoRendererWrapper() {}
virtual void SetSize(int width, int height) OVERRIDE {
jni()->CallVoidMethod(*j_callbacks_, j_set_size_id_, width, height);
CHECK_EXCEPTION(jni(), "");
}
virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE {
ScopedLocalRef<jobject> j_frame(jni(), CricketToJavaFrame(frame));
jni()->CallVoidMethod(*j_callbacks_, j_render_frame_id_, *j_frame);
CHECK_EXCEPTION(jni(), "");
}
private:
// Return a VideoRenderer.I420Frame referring to the data in |frame|.
jobject CricketToJavaFrame(const cricket::VideoFrame* frame) {
ScopedLocalRef<jintArray> strides(jni(), jni()->NewIntArray(3));
jint* strides_array = jni()->GetIntArrayElements(*strides, NULL);
strides_array[0] = frame->GetYPitch();
strides_array[1] = frame->GetUPitch();
strides_array[2] = frame->GetVPitch();
jni()->ReleaseIntArrayElements(*strides, strides_array, 0);
ScopedLocalRef<jobjectArray> planes(
jni(), jni()->NewObjectArray(3, *j_byte_buffer_class_, NULL));
ScopedLocalRef<jobject> y_buffer(jni(), jni()->NewDirectByteBuffer(
const_cast<uint8*>(frame->GetYPlane()),
frame->GetYPitch() * frame->GetHeight()));
ScopedLocalRef<jobject> u_buffer(jni(), jni()->NewDirectByteBuffer(
const_cast<uint8*>(frame->GetUPlane()), frame->GetChromaSize()));
ScopedLocalRef<jobject> v_buffer(jni(), 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);
return jni()->NewObject(
*j_frame_class_, j_frame_ctor_id_,
frame->GetWidth(), frame->GetHeight(), *strides, *planes);
}
JNIEnv* jni() {
return AttachCurrentThreadIfNeeded();
}
ScopedGlobalRef<jobject> j_callbacks_;
jmethodID j_set_size_id_;
jmethodID j_render_frame_id_;
ScopedGlobalRef<jclass> j_frame_class_;
jmethodID j_frame_ctor_id_;
ScopedGlobalRef<jclass> j_byte_buffer_class_;
};
} // anonymous namespace
// 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(talk_base::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) {
delete g_class_reference_holder;
g_class_reference_holder = NULL;
CHECK(talk_base::CleanupSSL(), "Failed to CleanupSSL()");
}
static DataChannelInterface* ExtractNativeDC(JNIEnv* jni, jobject j_dc) {
jfieldID native_dc_id = GetFieldID(jni,
GetObjectClass(jni, j_dc), "nativeDataChannel", "J");
jlong j_d = GetLongField(jni, j_dc, native_dc_id);
return reinterpret_cast<DataChannelInterface*>(j_d);
}
JOW(jlong, DataChannel_registerObserverNative)(
JNIEnv* jni, jobject j_dc, jobject j_observer) {
talk_base::scoped_ptr<DataChannelObserverWrapper> observer(
new DataChannelObserverWrapper(jni, j_observer));
ExtractNativeDC(jni, j_dc)->RegisterObserver(observer.get());
return reinterpret_cast<jlong>(observer.release());
}
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();
CHECK(buffered_amount <= std::numeric_limits<int64>::max(),
"buffered_amount overflowed jlong!");
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(
talk_base::Buffer(bytes, jni->GetArrayLength(data)),
binary));
jni->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
return ret;
}
JOW(void, DataChannel_dispose)(JNIEnv* jni, jobject j_dc) {
CHECK_RELEASE(ExtractNativeDC(jni, j_dc));
}
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) {
webrtc::Trace::set_level_filter(nativeLevels);
#ifdef ANDROID
if (path != "logcat:") {
#endif
CHECK(webrtc::Trace::SetTraceFile(path.c_str(), false) == 0,
"SetTraceFile failed");
#ifdef ANDROID
} 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
}
talk_base::LogMessage::LogToDebug(nativeSeverity);
}
JOW(void, PeerConnection_freePeerConnection)(JNIEnv*, jclass, jlong j_p) {
CHECK_RELEASE(reinterpret_cast<PeerConnectionInterface*>(j_p));
}
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) {
CHECK_RELEASE(reinterpret_cast<MediaSourceInterface*>(j_p));
}
JOW(void, VideoCapturer_free)(JNIEnv*, jclass, jlong j_p) {
delete reinterpret_cast<cricket::VideoCapturer*>(j_p);
}
JOW(void, VideoRenderer_free)(JNIEnv*, jclass, jlong j_p) {
delete reinterpret_cast<VideoRendererWrapper*>(j_p);
}
JOW(void, MediaStreamTrack_free)(JNIEnv*, jclass, jlong j_p) {
CHECK_RELEASE(reinterpret_cast<MediaStreamTrackInterface*>(j_p));
}
JOW(jboolean, MediaStream_nativeAddAudioTrack)(
JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
return reinterpret_cast<MediaStreamInterface*>(pointer)->AddTrack(
reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
}
JOW(jboolean, MediaStream_nativeAddVideoTrack)(
JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
return reinterpret_cast<MediaStreamInterface*>(pointer)
->AddTrack(reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer));
}
JOW(jboolean, MediaStream_nativeRemoveAudioTrack)(
JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) {
return reinterpret_cast<MediaStreamInterface*>(pointer)->RemoveTrack(
reinterpret_cast<AudioTrackInterface*>(j_audio_track_pointer));
}
JOW(jboolean, MediaStream_nativeRemoveVideoTrack)(
JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) {
return reinterpret_cast<MediaStreamInterface*>(pointer)->RemoveTrack(
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) {
CHECK_RELEASE(reinterpret_cast<MediaStreamInterface*>(j_p));
}
JOW(jlong, PeerConnectionFactory_nativeCreateObserver)(
JNIEnv * jni, jclass, jobject j_observer) {
return (jlong)new PCOJava(jni, j_observer);
}
#ifdef ANDROID
JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)(
JNIEnv* jni, jclass, jobject context) {
CHECK(g_jvm, "JNI_OnLoad failed to run?");
bool failure = false;
failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context);
failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
return !failure;
}
#endif // ANDROID
JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)(
JNIEnv* jni, jclass) {
webrtc::Trace::CreateTrace();
talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
webrtc::CreatePeerConnectionFactory());
return (jlong)factory.release();
}
JOW(void, PeerConnectionFactory_freeFactory)(JNIEnv*, jclass, jlong j_p) {
CHECK_RELEASE(reinterpret_cast<PeerConnectionFactoryInterface*>(j_p));
webrtc::Trace::ReturnTrace();
}
JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)(
JNIEnv* jni, jclass, jlong native_factory, jstring label) {
talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
talk_base::scoped_refptr<MediaStreamInterface> stream(
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) {
talk_base::scoped_ptr<ConstraintsWrapper> constraints(
new ConstraintsWrapper(jni, j_constraints));
talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
talk_base::scoped_refptr<VideoSourceInterface> source(
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) {
talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
talk_base::scoped_refptr<VideoTrackInterface> track(
factory->CreateVideoTrack(
JavaToStdString(jni, id),
reinterpret_cast<VideoSourceInterface*>(native_source)));
return (jlong)track.release();
}
JOW(jlong, PeerConnectionFactory_nativeCreateAudioTrack)(
JNIEnv* jni, jclass, jlong native_factory, jstring id) {
talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory));
talk_base::scoped_refptr<AudioTrackInterface> track(
factory->CreateAudioTrack(JavaToStdString(jni, id), NULL));
return (jlong)track.release();
}
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);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
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)) {
CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
jobject j_ice_server = jni->CallObjectMethod(iterator, iterator_next);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
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);
}
CHECK_EXCEPTION(jni, "error during CallBooleanMethod");
}
JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)(
JNIEnv *jni, jclass, jlong factory, jobject j_ice_servers,
jobject j_constraints, jlong observer_p) {
talk_base::scoped_refptr<PeerConnectionFactoryInterface> f(
reinterpret_cast<PeerConnectionFactoryInterface*>(factory));
PeerConnectionInterface::IceServers servers;
JavaIceServersToJsepIceServers(jni, j_ice_servers, &servers);
PCOJava* observer = reinterpret_cast<PCOJava*>(observer_p);
observer->SetConstraints(new ConstraintsWrapper(jni, j_constraints));
talk_base::scoped_refptr<PeerConnectionInterface> pc(f->CreatePeerConnection(
servers, observer->constraints(), NULL, observer));
return (jlong)pc.release();
}
static talk_base::scoped_refptr<PeerConnectionInterface> ExtractNativePC(
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);
return talk_base::scoped_refptr<PeerConnectionInterface>(
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;
}
JOW(jobject, PeerConnection_createDataChannel)(
JNIEnv* jni, jobject j_pc, jstring j_label, jobject j_init) {
DataChannelInit init = JavaDataChannelInitToNative(jni, j_init);
talk_base::scoped_refptr<DataChannelInterface> channel(
ExtractNativePC(jni, j_pc)->CreateDataChannel(
JavaToStdString(jni, j_label), &init));
// 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());
CHECK(nativeChannelPtr, "Failed to create DataChannel");
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(
j_data_channel_class, j_data_channel_ctor, nativeChannelPtr);
CHECK_EXCEPTION(jni, "error during NewObject");
// Channel is now owned by Java object, and will be freed from there.
int bumped_count = channel->AddRef();
CHECK(bumped_count == 2, "Unexpected refcount");
return j_channel;
}
JOW(void, PeerConnection_createOffer)(
JNIEnv* jni, jobject j_pc, jobject j_observer, jobject j_constraints) {
ConstraintsWrapper* constraints =
new ConstraintsWrapper(jni, j_constraints);
talk_base::scoped_refptr<CreateSdpObserverWrapper> observer(
new talk_base::RefCountedObject<CreateSdpObserverWrapper>(
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);
talk_base::scoped_refptr<CreateSdpObserverWrapper> observer(
new talk_base::RefCountedObject<CreateSdpObserverWrapper>(
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);
CHECK_EXCEPTION(jni, "error during CallObjectMethod");
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) {
talk_base::scoped_refptr<SetSdpObserverWrapper> observer(
new talk_base::RefCountedObject<SetSdpObserverWrapper>(
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) {
talk_base::scoped_refptr<SetSdpObserverWrapper> observer(
new talk_base::RefCountedObject<SetSdpObserverWrapper>(
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);
talk_base::scoped_ptr<ConstraintsWrapper> constraints(
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);
talk_base::scoped_ptr<IceCandidateInterface> candidate(
webrtc::CreateIceCandidate(sdp_mid, j_sdp_mline_index, sdp, NULL));
return ExtractNativePC(jni, j_pc)->AddIceCandidate(candidate.get());
}
JOW(jboolean, PeerConnection_nativeAddLocalStream)(
JNIEnv* jni, jobject j_pc, jlong native_stream, jobject j_constraints) {
talk_base::scoped_ptr<ConstraintsWrapper> constraints(
new ConstraintsWrapper(jni, j_constraints));
return ExtractNativePC(jni, j_pc)->AddStream(
reinterpret_cast<MediaStreamInterface*>(native_stream),
constraints.get());
}
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) {
talk_base::scoped_refptr<StatsObserverWrapper> observer(
new talk_base::RefCountedObject<StatsObserverWrapper>(jni, j_observer));
return ExtractNativePC(jni, j_pc)->GetStats(
observer, reinterpret_cast<MediaStreamTrackInterface*>(native_track));
}
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);
}
JOW(jobject, PeerGathering_iceGatheringState)(JNIEnv* jni, jobject j_pc) {
PeerConnectionInterface::IceGatheringState state =
ExtractNativePC(jni, j_pc)->ice_gathering_state();
return JavaEnumFromIndex(jni, "PeerGathering$IceGatheringState", state);
}
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) {
talk_base::scoped_refptr<MediaSourceInterface> p(
reinterpret_cast<MediaSourceInterface*>(j_p));
return JavaEnumFromIndex(jni, "MediaSource$State", p->state());
}
JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)(
JNIEnv* jni, jclass, jstring j_device_name) {
std::string device_name = JavaToStdString(jni, j_device_name);
talk_base::scoped_ptr<cricket::DeviceManagerInterface> device_manager(
cricket::DeviceManagerFactory::Create());
CHECK(device_manager->Init(), "DeviceManager::Init() failed");
cricket::Device device;
if (!device_manager->GetVideoCaptureDevice(device_name, &device)) {
LOG(LS_ERROR) << "GetVideoCaptureDevice failed";
return 0;
}
talk_base::scoped_ptr<cricket::VideoCapturer> capturer(
device_manager->CreateVideoCapturer(device));
return (jlong)capturer.release();
}
JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)(
JNIEnv* jni, jclass, int x, int y) {
talk_base::scoped_ptr<VideoRendererWrapper> renderer(
VideoRendererWrapper::Create(
cricket::VideoRendererFactory::CreateGuiVideoRenderer(x, y)));
return (jlong)renderer.release();
}
JOW(jlong, VideoRenderer_nativeWrapVideoRenderer)(
JNIEnv* jni, jclass, jobject j_callbacks) {
talk_base::scoped_ptr<JavaVideoRendererWrapper> renderer(
new JavaVideoRendererWrapper(jni, j_callbacks));
return (jlong)renderer.release();
}
JOW(jstring, MediaStreamTrack_nativeId)(JNIEnv* jni, jclass, jlong j_p) {
return JavaStringFromStdString(
jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->id());
}
JOW(jstring, MediaStreamTrack_nativeKind)(JNIEnv* jni, jclass, jlong j_p) {
return JavaStringFromStdString(
jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->kind());
}
JOW(jboolean, MediaStreamTrack_nativeEnabled)(JNIEnv* jni, jclass, jlong j_p) {
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)->enabled();
}
JOW(jobject, MediaStreamTrack_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
return JavaEnumFromIndex(
jni,
"MediaStreamTrack$State",
reinterpret_cast<MediaStreamTrackInterface*>(j_p)->state());
}
JOW(jboolean, MediaStreamTrack_nativeSetState)(
JNIEnv* jni, jclass, jlong j_p, jint j_new_state) {
MediaStreamTrackInterface::TrackState new_state =
(MediaStreamTrackInterface::TrackState)j_new_state;
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)
->set_state(new_state);
}
JOW(jboolean, MediaStreamTrack_nativeSetEnabled)(
JNIEnv* jni, jclass, jlong j_p, jboolean enabled) {
return reinterpret_cast<MediaStreamTrackInterface*>(j_p)
->set_enabled(enabled);
}
JOW(void, VideoTrack_nativeAddRenderer)(
JNIEnv* jni, jclass,
jlong j_video_track_pointer, jlong j_renderer_pointer) {
reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer)->AddRenderer(
reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
}
JOW(void, VideoTrack_nativeRemoveRenderer)(
JNIEnv* jni, jclass,
jlong j_video_track_pointer, jlong j_renderer_pointer) {
reinterpret_cast<VideoTrackInterface*>(j_video_track_pointer)->RemoveRenderer(
reinterpret_cast<VideoRendererInterface*>(j_renderer_pointer));
}