/* * 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 #undef JNIEXPORT #define JNIEXPORT __attribute__((visibility("default"))) #include #include #include #include #include #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 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(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(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(ptr); // If the unsigned value fits in the signed type, return it directly. if (u64 <= std::numeric_limits::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::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::const_iterator it = classes_.begin(); it != classes_.end(); ++it) { jni->DeleteGlobalRef(it->second); } classes_.clear(); } jclass GetClass(const std::string& name) { std::map::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(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 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(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 // 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 // T is jclass, jobject, jintArray, etc. class ScopedGlobalRef { public: explicit ScopedGlobalRef(JNIEnv* jni, T obj) : obj_(static_cast(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 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_, "", "(J)V")), j_audio_track_class_(jni, FindClass(jni, "org/webrtc/AudioTrack")), j_audio_track_ctor_(GetMethodID( jni, *j_audio_track_class_, "", "(J)V")), j_video_track_class_(jni, FindClass(jni, "org/webrtc/VideoTrack")), j_video_track_ctor_(GetMethodID( jni, *j_video_track_class_, "", "(J)V")), j_data_channel_class_(jni, FindClass(jni, "org/webrtc/DataChannel")), j_data_channel_ctor_(GetMethodID( jni, *j_data_channel_class_, "", "(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, "", "(Ljava/lang/String;ILjava/lang/String;)V"); ScopedLocalRef j_mid( jni(), JavaStringFromStdString(jni(), candidate->sdp_mid())); ScopedLocalRef j_sdp(jni(), JavaStringFromStdString(jni(), sdp)); ScopedLocalRef 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 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 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 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 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 id( jni(), JavaStringFromStdString(jni(), track->id())); ScopedLocalRef 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 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 id( jni(), JavaStringFromStdString(jni(), track->id())); ScopedLocalRef 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 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 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 j_observer_global_; const ScopedGlobalRef j_observer_class_; const ScopedGlobalRef j_media_stream_class_; const jmethodID j_media_stream_ctor_; const ScopedGlobalRef j_audio_track_class_; const jmethodID j_audio_track_ctor_; const ScopedGlobalRef j_video_track_class_; const jmethodID j_video_track_ctor_; const ScopedGlobalRef j_data_channel_class_; const jmethodID j_data_channel_ctor_; typedef std::map NativeToJavaStreamsMap; NativeToJavaStreamsMap streams_; // C++ -> Java streams. talk_base::scoped_ptr 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> 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( 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( 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 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 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, "", "(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 // 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 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 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 constraints_; const ScopedGlobalRef j_observer_global_; const ScopedGlobalRef j_observer_class_; }; class CreateSdpObserverWrapper : public SdpObserverWrapper { 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 { 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_, "", "(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(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 j_observer_global_; const ScopedGlobalRef j_observer_class_; const ScopedGlobalRef 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_, "", "(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_, "", "(Ljava/lang/String;Ljava/lang/String;)V")) { } virtual ~StatsObserverWrapper() {} virtual void OnComplete(const std::vector& reports) OVERRIDE { ScopedLocalRef 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& 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 j_id( jni, JavaStringFromStdString(jni, report.id)); ScopedLocalRef j_type( jni, JavaStringFromStdString(jni, report.type)); ScopedLocalRef j_values( jni, ValuesToJava(jni, report.values)); ScopedLocalRef 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 j_name( jni, JavaStringFromStdString(jni, value.name)); ScopedLocalRef j_value( jni, JavaStringFromStdString(jni, value.value)); ScopedLocalRef 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 j_observer_global_; const ScopedGlobalRef j_observer_class_; const ScopedGlobalRef j_stats_report_class_; const jmethodID j_stats_report_ctor_; const ScopedGlobalRef 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 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_, "", "(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 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 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 planes( jni(), jni()->NewObjectArray(3, *j_byte_buffer_class_, NULL)); ScopedLocalRef y_buffer(jni(), jni()->NewDirectByteBuffer( const_cast(frame->GetYPlane()), frame->GetYPitch() * frame->GetHeight())); ScopedLocalRef u_buffer(jni(), jni()->NewDirectByteBuffer( const_cast(frame->GetUPlane()), frame->GetChromaSize())); ScopedLocalRef v_buffer(jni(), jni()->NewDirectByteBuffer( const_cast(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 j_callbacks_; jmethodID j_set_size_id_; jmethodID j_render_frame_id_; ScopedGlobalRef j_frame_class_; jmethodID j_frame_ctor_id_; ScopedGlobalRef 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(&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(j_d); } JOW(jlong, DataChannel_registerObserverNative)( JNIEnv* jni, jobject j_dc, jobject j_observer) { talk_base::scoped_ptr observer( new DataChannelObserverWrapper(jni, j_observer)); ExtractNativeDC(jni, j_dc)->RegisterObserver(observer.get()); return reinterpret_cast(observer.release()); } JOW(void, DataChannel_unregisterObserverNative)( JNIEnv* jni, jobject j_dc, jlong native_observer) { ExtractNativeDC(jni, j_dc)->UnregisterObserver(); delete reinterpret_cast(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::max(), "buffered_amount overflowed jlong!"); return static_cast(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(j_p)); } JOW(void, PeerConnection_freeObserver)(JNIEnv*, jclass, jlong j_p) { PCOJava* p = reinterpret_cast(j_p); delete p; } JOW(void, MediaSource_free)(JNIEnv*, jclass, jlong j_p) { CHECK_RELEASE(reinterpret_cast(j_p)); } JOW(void, VideoCapturer_free)(JNIEnv*, jclass, jlong j_p) { delete reinterpret_cast(j_p); } JOW(void, VideoRenderer_free)(JNIEnv*, jclass, jlong j_p) { delete reinterpret_cast(j_p); } JOW(void, MediaStreamTrack_free)(JNIEnv*, jclass, jlong j_p) { CHECK_RELEASE(reinterpret_cast(j_p)); } JOW(jboolean, MediaStream_nativeAddAudioTrack)( JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) { return reinterpret_cast(pointer)->AddTrack( reinterpret_cast(j_audio_track_pointer)); } JOW(jboolean, MediaStream_nativeAddVideoTrack)( JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) { return reinterpret_cast(pointer) ->AddTrack(reinterpret_cast(j_video_track_pointer)); } JOW(jboolean, MediaStream_nativeRemoveAudioTrack)( JNIEnv* jni, jclass, jlong pointer, jlong j_audio_track_pointer) { return reinterpret_cast(pointer)->RemoveTrack( reinterpret_cast(j_audio_track_pointer)); } JOW(jboolean, MediaStream_nativeRemoveVideoTrack)( JNIEnv* jni, jclass, jlong pointer, jlong j_video_track_pointer) { return reinterpret_cast(pointer)->RemoveTrack( reinterpret_cast(j_video_track_pointer)); } JOW(jstring, MediaStream_nativeLabel)(JNIEnv* jni, jclass, jlong j_p) { return JavaStringFromStdString( jni, reinterpret_cast(j_p)->label()); } JOW(void, MediaStream_free)(JNIEnv*, jclass, jlong j_p) { CHECK_RELEASE(reinterpret_cast(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 factory( webrtc::CreatePeerConnectionFactory()); return (jlong)factory.release(); } JOW(void, PeerConnectionFactory_freeFactory)(JNIEnv*, jclass, jlong j_p) { CHECK_RELEASE(reinterpret_cast(j_p)); webrtc::Trace::ReturnTrace(); } JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)( JNIEnv* jni, jclass, jlong native_factory, jstring label) { talk_base::scoped_refptr factory( reinterpret_cast(native_factory)); talk_base::scoped_refptr 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 constraints( new ConstraintsWrapper(jni, j_constraints)); talk_base::scoped_refptr factory( reinterpret_cast(native_factory)); talk_base::scoped_refptr source( factory->CreateVideoSource( reinterpret_cast(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 factory( reinterpret_cast(native_factory)); talk_base::scoped_refptr track( factory->CreateVideoTrack( JavaToStdString(jni, id), reinterpret_cast(native_source))); return (jlong)track.release(); } JOW(jlong, PeerConnectionFactory_nativeCreateAudioTrack)( JNIEnv* jni, jclass, jlong native_factory, jstring id) { talk_base::scoped_refptr factory( reinterpret_cast(native_factory)); talk_base::scoped_refptr 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( GetObjectField(jni, j_ice_server, j_ice_server_uri_id)); jstring username = reinterpret_cast( GetObjectField(jni, j_ice_server, j_ice_server_username_id)); jstring password = reinterpret_cast( 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 f( reinterpret_cast(factory)); PeerConnectionInterface::IceServers servers; JavaIceServersToJsepIceServers(jni, j_ice_servers, &servers); PCOJava* observer = reinterpret_cast(observer_p); observer->SetConstraints(new ConstraintsWrapper(jni, j_constraints)); talk_base::scoped_refptr pc(f->CreatePeerConnection( servers, observer->constraints(), NULL, observer)); return (jlong)pc.release(); } static talk_base::scoped_refptr 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( reinterpret_cast(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 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, "", "(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 observer( new talk_base::RefCountedObject( 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 observer( new talk_base::RefCountedObject( 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 observer( new talk_base::RefCountedObject( jni, j_observer, reinterpret_cast(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 observer( new talk_base::RefCountedObject( jni, j_observer, reinterpret_cast(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 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 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 constraints( new ConstraintsWrapper(jni, j_constraints)); return ExtractNativePC(jni, j_pc)->AddStream( reinterpret_cast(native_stream), constraints.get()); } JOW(void, PeerConnection_nativeRemoveLocalStream)( JNIEnv* jni, jobject j_pc, jlong native_stream) { ExtractNativePC(jni, j_pc)->RemoveStream( reinterpret_cast(native_stream)); } JOW(bool, PeerConnection_nativeGetStats)( JNIEnv* jni, jobject j_pc, jobject j_observer, jlong native_track) { talk_base::scoped_refptr observer( new talk_base::RefCountedObject(jni, j_observer)); return ExtractNativePC(jni, j_pc)->GetStats( observer, reinterpret_cast(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 p( reinterpret_cast(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 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 capturer( device_manager->CreateVideoCapturer(device)); return (jlong)capturer.release(); } JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)( JNIEnv* jni, jclass, int x, int y) { talk_base::scoped_ptr 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 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(j_p)->id()); } JOW(jstring, MediaStreamTrack_nativeKind)(JNIEnv* jni, jclass, jlong j_p) { return JavaStringFromStdString( jni, reinterpret_cast(j_p)->kind()); } JOW(jboolean, MediaStreamTrack_nativeEnabled)(JNIEnv* jni, jclass, jlong j_p) { return reinterpret_cast(j_p)->enabled(); } JOW(jobject, MediaStreamTrack_nativeState)(JNIEnv* jni, jclass, jlong j_p) { return JavaEnumFromIndex( jni, "MediaStreamTrack$State", reinterpret_cast(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(j_p) ->set_state(new_state); } JOW(jboolean, MediaStreamTrack_nativeSetEnabled)( JNIEnv* jni, jclass, jlong j_p, jboolean enabled) { return reinterpret_cast(j_p) ->set_enabled(enabled); } JOW(void, VideoTrack_nativeAddRenderer)( JNIEnv* jni, jclass, jlong j_video_track_pointer, jlong j_renderer_pointer) { reinterpret_cast(j_video_track_pointer)->AddRenderer( reinterpret_cast(j_renderer_pointer)); } JOW(void, VideoTrack_nativeRemoveRenderer)( JNIEnv* jni, jclass, jlong j_video_track_pointer, jlong j_renderer_pointer) { reinterpret_cast(j_video_track_pointer)->RemoveRenderer( reinterpret_cast(j_renderer_pointer)); }