From 303b3c21a4adf67359734b1bfbf5bdb9b25ad6c7 Mon Sep 17 00:00:00 2001 From: skvlad Date: Thu, 24 Mar 2016 19:36:46 -0700 Subject: [PATCH] Added the JNI interface to get and set RtpParameters and the maximum bitrate limits. Defined a JavaCollection convenience class to simplify iterating over collections from within JNI code Follow-up to https://codereview.webrtc.org/1788583004/. BUG= Review URL: https://codereview.webrtc.org/1819553002 Cr-Commit-Position: refs/heads/master@{#12125} --- .../src/org/webrtc/PeerConnectionTest.java | 20 +++ webrtc/api/java/jni/jni_helpers.cc | 85 +++++++++++-- webrtc/api/java/jni/jni_helpers.h | 61 +++++++++ webrtc/api/java/jni/peerconnection_jni.cc | 117 ++++++++++++++---- .../java/src/org/webrtc/RtpParameters.java | 28 +++++ webrtc/api/java/src/org/webrtc/RtpSender.java | 13 ++ 6 files changed, 289 insertions(+), 35 deletions(-) create mode 100644 webrtc/api/java/src/org/webrtc/RtpParameters.java diff --git a/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java b/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java index ff98d7d96a..ebeeecd9a9 100644 --- a/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java +++ b/webrtc/api/androidtests/src/org/webrtc/PeerConnectionTest.java @@ -694,6 +694,26 @@ public class PeerConnectionTest extends ActivityTestCase { assertEquals( PeerConnection.SignalingState.STABLE, answeringPC.signalingState()); + // Set a bitrate limit for the outgoing video stream for the offerer. + RtpSender videoSender = null; + for (RtpSender sender : offeringPC.getSenders()) { + if (sender.track().kind().equals("video")) { + videoSender = sender; + } + } + assertNotNull(videoSender); + RtpParameters rtpParameters = videoSender.getParameters(); + assertNotNull(rtpParameters); + assertEquals(1, rtpParameters.encodings.size()); + assertNull(rtpParameters.encodings.get(0).maxBitrateBps); + + rtpParameters.encodings.get(0).maxBitrateBps = 300000; + assertTrue(videoSender.setParameters(rtpParameters)); + + // Verify that we can read back the updated value. + rtpParameters = videoSender.getParameters(); + assertEquals(300000, (int) rtpParameters.encodings.get(0).maxBitrateBps); + // Test send & receive UTF-8 text. answeringExpectations.expectMessage( ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false); diff --git a/webrtc/api/java/jni/jni_helpers.cc b/webrtc/api/java/jni/jni_helpers.cc index 32092e65f8..f42abc703e 100644 --- a/webrtc/api/java/jni/jni_helpers.cc +++ b/webrtc/api/java/jni/jni_helpers.cc @@ -34,10 +34,10 @@ JavaVM *GetJVM() { // Return a |JNIEnv*| usable on this thread or NULL if this thread is detached. JNIEnv* GetEnv() { - void* env = NULL; + void* env = nullptr; jint status = g_jvm->GetEnv(&env, JNI_VERSION_1_6); - RTC_CHECK(((env != NULL) && (status == JNI_OK)) || - ((env == NULL) && (status == JNI_EDETACHED))) + RTC_CHECK(((env != nullptr) && (status == JNI_OK)) || + ((env == nullptr) && (status == JNI_EDETACHED))) << "Unexpected GetEnv return: " << status << ":" << env; return reinterpret_cast(env); } @@ -109,12 +109,12 @@ JNIEnv* AttachCurrentThreadIfNeeded() { JavaVMAttachArgs args; args.version = JNI_VERSION_1_6; args.name = &name[0]; - args.group = NULL; + args.group = nullptr; // Deal with difference in signatures between Oracle's jni.h and Android's. #ifdef _JAVASOFT_JNI_H_ // Oracle's jni.h violates the JNI spec! - void* env = NULL; + void* env = nullptr; #else - JNIEnv* env = NULL; + JNIEnv* env = nullptr; #endif RTC_CHECK(!g_jvm->AttachCurrentThread(&env, &args)) << "Failed to attach thread"; @@ -215,7 +215,7 @@ jstring JavaStringFromStdString(JNIEnv* jni, const std::string& native) { // Given a (UTF-16) jstring return a new UTF-8 native string. std::string JavaToStdString(JNIEnv* jni, const jstring& j_string) { - const char* chars = jni->GetStringUTFChars(j_string, NULL); + const char* chars = jni->GetStringUTFChars(j_string, nullptr); CHECK_EXCEPTION(jni) << "Error during GetStringUTFChars"; std::string str(chars, jni->GetStringUTFLength(j_string)); CHECK_EXCEPTION(jni) << "Error during GetStringUTFLength"; @@ -269,7 +269,76 @@ ScopedLocalRefFrame::ScopedLocalRefFrame(JNIEnv* jni) : jni_(jni) { RTC_CHECK(!jni_->PushLocalFrame(0)) << "Failed to PushLocalFrame"; } ScopedLocalRefFrame::~ScopedLocalRefFrame() { - jni_->PopLocalFrame(NULL); + jni_->PopLocalFrame(nullptr); +} + +// Creates an iterator representing the end of any collection. +Iterable::Iterator::Iterator() : iterator_(nullptr) {} + +// Creates an iterator pointing to the beginning of the specified collection. +Iterable::Iterator::Iterator(JNIEnv* jni, jobject iterable) : jni_(jni) { + jclass j_class = GetObjectClass(jni, iterable); + jmethodID iterator_id = + GetMethodID(jni, j_class, "iterator", "()Ljava/util/Iterator;"); + iterator_ = jni->CallObjectMethod(iterable, iterator_id); + CHECK_EXCEPTION(jni) << "error during CallObjectMethod"; + RTC_CHECK(iterator_ != nullptr); + + jclass iterator_class = GetObjectClass(jni, iterator_); + has_next_id_ = GetMethodID(jni, iterator_class, "hasNext", "()Z"); + next_id_ = GetMethodID(jni, iterator_class, "next", "()Ljava/lang/Object;"); + + // Start at the first element in the collection. + ++(*this); +} + +// Move constructor - necessary to be able to return iterator types from +// functions. +Iterable::Iterator::Iterator(Iterator&& other) + : jni_(std::move(other.jni_)), + iterator_(std::move(other.iterator_)), + value_(std::move(other.value_)), + has_next_id_(std::move(other.has_next_id_)), + next_id_(std::move(other.next_id_)), + thread_checker_(std::move(other.thread_checker_)){}; + +// Advances the iterator one step. +Iterable::Iterator& Iterable::Iterator::operator++() { + RTC_CHECK(thread_checker_.CalledOnValidThread()); + if (AtEnd()) { + // Can't move past the end. + return *this; + } + bool has_next = jni_->CallBooleanMethod(iterator_, has_next_id_); + CHECK_EXCEPTION(jni_) << "error during CallBooleanMethod"; + if (!has_next) { + iterator_ = nullptr; + value_ = nullptr; + return *this; + } + + value_ = jni_->CallObjectMethod(iterator_, next_id_); + CHECK_EXCEPTION(jni_) << "error during CallObjectMethod"; + return *this; +} + +// Provides a way to compare the iterator with itself and with the end iterator. +// Note: all other comparison results are undefined, just like for C++ input +// iterators. +bool Iterable::Iterator::operator==(const Iterable::Iterator& other) { + // Two different active iterators should never be compared. + RTC_DCHECK(this == &other || AtEnd() || other.AtEnd()); + return AtEnd() == other.AtEnd(); +} + +jobject Iterable::Iterator::operator*() { + RTC_CHECK(!AtEnd()); + return value_; +} + +bool Iterable::Iterator::AtEnd() const { + RTC_CHECK(thread_checker_.CalledOnValidThread()); + return jni_ == nullptr || IsNull(jni_, iterator_); } } // namespace webrtc_jni diff --git a/webrtc/api/java/jni/jni_helpers.h b/webrtc/api/java/jni/jni_helpers.h index 87c5ca48f2..98dcc38b53 100644 --- a/webrtc/api/java/jni/jni_helpers.h +++ b/webrtc/api/java/jni/jni_helpers.h @@ -17,7 +17,9 @@ #include #include +#include "webrtc/base/constructormagic.h" #include "webrtc/base/checks.h" +#include "webrtc/base/thread_checker.h" // Abort the process if |jni| has a Java exception pending. // This macros uses the comma operator to execute ExceptionDescribe @@ -122,6 +124,65 @@ class ScopedGlobalRef { T obj_; }; +// Provides a convenient way to iterate over a Java Iterable using the +// C++ range-for loop. +// E.g. for (jobject value : Iterable(jni, j_iterable)) { ... } +// Note: Since Java iterators cannot be duplicated, the iterator class is not +// copyable to prevent creating multiple C++ iterators that refer to the same +// Java iterator. +class Iterable { + public: + Iterable(JNIEnv* jni, jobject iterable) : jni_(jni), iterable_(iterable) {} + + class Iterator { + public: + // Creates an iterator representing the end of any collection. + Iterator(); + // Creates an iterator pointing to the beginning of the specified + // collection. + Iterator(JNIEnv* jni, jobject iterable); + + // Move constructor - necessary to be able to return iterator types from + // functions. + Iterator(Iterator&& other); + + // Move assignment should not be used. + Iterator& operator=(Iterator&&) = delete; + + // Advances the iterator one step. + Iterator& operator++(); + + // Provides a way to compare the iterator with itself and with the end + // iterator. + // Note: all other comparison results are undefined, just like for C++ input + // iterators. + bool operator==(const Iterator& other); + bool operator!=(const Iterator& other) { return !(*this == other); } + jobject operator*(); + + private: + bool AtEnd() const; + + JNIEnv* jni_ = nullptr; + jobject iterator_ = nullptr; + jobject value_ = nullptr; + jmethodID has_next_id_ = nullptr; + jmethodID next_id_ = nullptr; + rtc::ThreadChecker thread_checker_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Iterator); + }; + + Iterable::Iterator begin() { return Iterable::Iterator(jni_, iterable_); } + Iterable::Iterator end() { return Iterable::Iterator(); } + + private: + JNIEnv* jni_; + jobject iterable_; + + RTC_DISALLOW_COPY_AND_ASSIGN(Iterable); +}; + } // namespace webrtc_jni #endif // WEBRTC_API_JAVA_JNI_JNI_HELPERS_H_ diff --git a/webrtc/api/java/jni/peerconnection_jni.cc b/webrtc/api/java/jni/peerconnection_jni.cc index d2fe15a0e5..0e5dd9ad6e 100644 --- a/webrtc/api/java/jni/peerconnection_jni.cc +++ b/webrtc/api/java/jni/peerconnection_jni.cc @@ -456,18 +456,7 @@ class ConstraintsWrapper : public MediaConstraintsInterface { 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"; + for (jobject entry : Iterable(jni, j_list)) { jmethodID get_key = GetMethodID(jni, GetObjectClass(jni, entry), "getKey", "()Ljava/lang/String;"); jstring j_key = reinterpret_cast( @@ -481,7 +470,6 @@ class ConstraintsWrapper : public MediaConstraintsInterface { field->push_back(Constraint(JavaToStdString(jni, j_key), JavaToStdString(jni, j_value))); } - CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; } Constraints mandatory_; @@ -1464,19 +1452,7 @@ static PeerConnectionInterface::ContinualGatheringPolicy 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"; + for (jobject j_ice_server : Iterable(jni, j_ice_servers)) { 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;"); @@ -1496,7 +1472,6 @@ static void JavaIceServersToJsepIceServers( server.password = JavaToStdString(jni, password); ice_servers->push_back(server); } - CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; } static void JavaRTCConfigurationToJsepRTCConfiguration( @@ -2051,6 +2026,94 @@ JOW(jlong, RtpSender_nativeGetTrack)(JNIEnv* jni, .release()); } +static bool JavaEncodingToJsepRtpEncodingParameters( + JNIEnv* jni, + jobject j_encodings, + std::vector* encodings) { + const int kBitrateUnlimited = -1; + jclass j_encoding_parameters_class = + jni->FindClass("org/webrtc/RtpParameters$Encoding"); + jfieldID bitrate_id = GetFieldID(jni, j_encoding_parameters_class, + "maxBitrateBps", "Ljava/lang/Integer;"); + jclass j_integer_class = jni->FindClass("java/lang/Integer"); + jmethodID int_value_id = GetMethodID(jni, j_integer_class, "intValue", "()I"); + + for (jobject j_encoding_parameters : Iterable(jni, j_encodings)) { + webrtc::RtpEncodingParameters encoding; + jobject j_bitrate = GetObjectField(jni, j_encoding_parameters, bitrate_id); + if (!IsNull(jni, j_bitrate)) { + int bitrate_value = jni->CallIntMethod(j_bitrate, int_value_id); + CHECK_EXCEPTION(jni) << "error during CallIntMethod"; + encoding.max_bitrate_bps = bitrate_value; + } else { + encoding.max_bitrate_bps = kBitrateUnlimited; + } + encodings->push_back(encoding); + } + return true; +} + +JOW(jboolean, RtpSender_nativeSetParameters) +(JNIEnv* jni, jclass, jlong j_rtp_sender_pointer, jobject j_parameters) { + if (IsNull(jni, j_parameters)) { + return false; + } + jclass parameters_class = jni->FindClass("org/webrtc/RtpParameters"); + jclass encoding_class = jni->FindClass("org/webrtc/RtpParameters$Encoding"); + jfieldID encodings_id = + GetFieldID(jni, parameters_class, "encodings", "Ljava/util/LinkedList;"); + + jobject j_encodings = GetObjectField(jni, j_parameters, encodings_id); + webrtc::RtpParameters parameters; + JavaEncodingToJsepRtpEncodingParameters(jni, j_encodings, + ¶meters.encodings); + return reinterpret_cast(j_rtp_sender_pointer) + ->SetParameters(parameters); +} + +JOW(jobject, RtpSender_nativeGetParameters) +(JNIEnv* jni, jclass, jlong j_rtp_sender_pointer) { + webrtc::RtpParameters parameters = + reinterpret_cast(j_rtp_sender_pointer) + ->GetParameters(); + + jclass parameters_class = jni->FindClass("org/webrtc/RtpParameters"); + jmethodID parameters_ctor = + GetMethodID(jni, parameters_class, "", "()V"); + jobject j_parameters = jni->NewObject(parameters_class, parameters_ctor); + CHECK_EXCEPTION(jni) << "error during NewObject"; + + jclass encoding_class = jni->FindClass("org/webrtc/RtpParameters$Encoding"); + jmethodID encoding_ctor = GetMethodID(jni, encoding_class, "", "()V"); + jfieldID encodings_id = + GetFieldID(jni, parameters_class, "encodings", "Ljava/util/LinkedList;"); + jobject j_encodings = GetObjectField(jni, j_parameters, encodings_id); + jmethodID add = GetMethodID(jni, GetObjectClass(jni, j_encodings), "add", + "(Ljava/lang/Object;)Z"); + jfieldID bitrate_id = + GetFieldID(jni, encoding_class, "maxBitrateBps", "Ljava/lang/Integer;"); + + jclass integer_class = jni->FindClass("java/lang/Integer"); + jmethodID integer_ctor = GetMethodID(jni, integer_class, "", "(I)V"); + + for (webrtc::RtpEncodingParameters encoding : parameters.encodings) { + jobject j_encoding_parameters = + jni->NewObject(encoding_class, encoding_ctor); + CHECK_EXCEPTION(jni) << "error during NewObject"; + if (encoding.max_bitrate_bps > 0) { + jobject j_bitrate_value = + jni->NewObject(integer_class, integer_ctor, encoding.max_bitrate_bps); + CHECK_EXCEPTION(jni) << "error during NewObject"; + jni->SetObjectField(j_encoding_parameters, bitrate_id, j_bitrate_value); + CHECK_EXCEPTION(jni) << "error during SetObjectField"; + } + jboolean added = + jni->CallBooleanMethod(j_encodings, add, j_encoding_parameters); + CHECK_EXCEPTION(jni) << "error during CallBooleanMethod"; + } + return j_parameters; +} + JOW(jstring, RtpSender_nativeId)( JNIEnv* jni, jclass, jlong j_rtp_sender_pointer) { return JavaStringFromStdString( diff --git a/webrtc/api/java/src/org/webrtc/RtpParameters.java b/webrtc/api/java/src/org/webrtc/RtpParameters.java new file mode 100644 index 0000000000..4dcdcc403b --- /dev/null +++ b/webrtc/api/java/src/org/webrtc/RtpParameters.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +import java.util.List; +import java.util.LinkedList; + +/** + * The parameters for an {@code RtpSender}, as defined in + * http://w3c.github.io/webrtc-pc/#rtcrtpsender-interface. + */ +public class RtpParameters { + public static class Encoding { public Integer maxBitrateBps; } + + public final LinkedList encodings; + + public RtpParameters() { + encodings = new LinkedList(); + } +} diff --git a/webrtc/api/java/src/org/webrtc/RtpSender.java b/webrtc/api/java/src/org/webrtc/RtpSender.java index 3950a59a2d..2c094ac0d7 100644 --- a/webrtc/api/java/src/org/webrtc/RtpSender.java +++ b/webrtc/api/java/src/org/webrtc/RtpSender.java @@ -46,6 +46,14 @@ public class RtpSender { return cachedTrack; } + public boolean setParameters(RtpParameters parameters) { + return nativeSetParameters(nativeRtpSender, parameters); + } + + public RtpParameters getParameters() { + return nativeGetParameters(nativeRtpSender); + } + public String id() { return nativeId(nativeRtpSender); } @@ -64,6 +72,11 @@ public class RtpSender { // Will be released in dispose() or setTrack(). private static native long nativeGetTrack(long nativeRtpSender); + private static native boolean nativeSetParameters(long nativeRtpSender, + RtpParameters parameters); + + private static native RtpParameters nativeGetParameters(long nativeRtpSender); + private static native String nativeId(long nativeRtpSender); private static native void free(long nativeRtpSender);