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}
This commit is contained in:
skvlad 2016-03-24 19:36:46 -07:00 committed by Commit bot
parent a49dc36976
commit 303b3c21a4
6 changed files with 289 additions and 35 deletions

View File

@ -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);

View File

@ -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<JNIEnv*>(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

View File

@ -17,7 +17,9 @@
#include <jni.h>
#include <string>
#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_

View File

@ -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<jstring>(
@ -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<webrtc::RtpEncodingParameters>* 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,
&parameters.encodings);
return reinterpret_cast<RtpSenderInterface*>(j_rtp_sender_pointer)
->SetParameters(parameters);
}
JOW(jobject, RtpSender_nativeGetParameters)
(JNIEnv* jni, jclass, jlong j_rtp_sender_pointer) {
webrtc::RtpParameters parameters =
reinterpret_cast<RtpSenderInterface*>(j_rtp_sender_pointer)
->GetParameters();
jclass parameters_class = jni->FindClass("org/webrtc/RtpParameters");
jmethodID parameters_ctor =
GetMethodID(jni, parameters_class, "<init>", "()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, "<init>", "()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, "<init>", "(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(

View File

@ -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<Encoding> encodings;
public RtpParameters() {
encodings = new LinkedList<Encoding>();
}
}

View File

@ -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);