PeerConnection(java): replace ScopedLocalRef with ScopedLocalRefFrame and fix a local reference leak in OnMessage.

Hopefully the approach of pushing/popping frames will be easier to avoid messing up than remembering to annotate every single local reference with a ScopedLocalRef.

BUG=2761
R=wu@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/6729004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5355 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
fischman@webrtc.org 2014-01-09 00:31:17 +00:00
parent 1794693ec8
commit 4177615e87

View File

@ -376,22 +376,20 @@ class WeakRef {
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 {
// Scope Java local references to the lifetime of this object. Use in all C++
// callbacks (i.e. entry points that don't originate in a Java callstack
// through a "native" method call).
class ScopedLocalRefFrame {
public:
ScopedLocalRef(JNIEnv* jni, T obj)
: jni_(jni), obj_(obj) {}
~ScopedLocalRef() {
jni_->DeleteLocalRef(obj_);
explicit ScopedLocalRefFrame(JNIEnv* jni) : jni_(jni) {
CHECK(!jni_->PushLocalFrame(0), "Failed to PushLocalFrame");
}
T operator*() const {
return obj_;
~ScopedLocalRefFrame() {
jni_->PopLocalFrame(NULL);
}
private:
JNIEnv* jni_;
T obj_;
};
// Scoped holder for global Java refs.
@ -418,11 +416,10 @@ jobject JavaEnumFromIndex(
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));
jobjectArray state_values = static_cast<jobjectArray>(
jni->CallStaticObjectMethod(state_class, state_values_id));
CHECK_EXCEPTION(jni, "error during CallStaticObjectMethod");
jobject ret = jni->GetObjectArrayElement(*state_values, index);
jobject ret = jni->GetObjectArrayElement(state_values, index);
CHECK_EXCEPTION(jni, "error during GetObjectArrayElement");
return ret;
}
@ -500,20 +497,20 @@ class PCOJava : public PeerConnectionObserver {
virtual ~PCOJava() {}
virtual void OnIceCandidate(const IceCandidateInterface* candidate) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
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));
jstring j_mid = JavaStringFromStdString(jni(), candidate->sdp_mid());
jstring j_sdp = JavaStringFromStdString(jni(), sdp);
jobject j_candidate = jni()->NewObject(
candidate_class, ctor, j_mid, candidate->sdp_mline_index(), j_sdp);
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);
jni()->CallVoidMethod(*j_observer_global_, m, j_candidate);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
@ -525,59 +522,63 @@ class PCOJava : public PeerConnectionObserver {
virtual void OnSignalingChange(
PeerConnectionInterface::SignalingState new_state) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
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);
jobject new_state_enum =
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 {
ScopedLocalRefFrame local_ref_frame(jni());
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);
jobject new_state_enum = 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 {
ScopedLocalRefFrame local_ref_frame(jni());
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);
jobject new_state_enum = 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));
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_stream = 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));
jstring id = JavaStringFromStdString(jni(), track->id());
jobject j_track = 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));
jobject audio_tracks = 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);
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, "");
}
@ -585,33 +586,34 @@ class PCOJava : public PeerConnectionObserver {
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));
jstring id = JavaStringFromStdString(jni(), track->id());
jobject j_track = 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));
jobject video_tracks = 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);
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);
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);
jni()->CallVoidMethod(*j_observer_global_, m, j_stream);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnRemoveStream(MediaStreamInterface* stream) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
NativeToJavaStreamsMap::iterator it = streams_.find(stream);
CHECK(it != streams_.end(), "unexpected stream: " << std::hex << stream);
@ -627,13 +629,14 @@ class PCOJava : public PeerConnectionObserver {
}
virtual void OnDataChannel(DataChannelInterface* channel) OVERRIDE {
ScopedLocalRef<jobject> j_channel(jni(), jni()->NewObject(
*j_data_channel_class_, j_data_channel_ctor_, (jlong)channel));
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_channel = 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);
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
@ -738,17 +741,16 @@ 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));
jstring j_description = 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()));
jstring j_type_string = JavaStringFromStdString(jni, desc->type());
jobject j_type = jni->CallStaticObjectMethod(
j_type_class, j_type_from_canonical, *j_type_string);
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");
@ -756,7 +758,7 @@ static jobject JavaSdpFromNativeSdp(
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);
j_sdp_class, j_sdp_ctor, j_type, j_description);
CHECK_EXCEPTION(jni, "error during NewObject");
return j_sdp;
}
@ -775,6 +777,7 @@ class SdpObserverWrapper : public T {
// Can't mark OVERRIDE because of templating.
virtual void OnSuccess() {
ScopedLocalRefFrame local_ref_frame(jni());
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onSetSuccess", "()V");
jni()->CallVoidMethod(*j_observer_global_, m);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
@ -782,11 +785,12 @@ class SdpObserverWrapper : public T {
// Can't mark OVERRIDE because of templating.
virtual void OnSuccess(SessionDescriptionInterface* desc) {
ScopedLocalRefFrame local_ref_frame(jni());
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);
jobject j_sdp = JavaSdpFromNativeSdp(jni(), desc);
jni()->CallVoidMethod(*j_observer_global_, m, j_sdp);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
@ -796,17 +800,16 @@ class SdpObserverWrapper : public T {
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);
jstring j_error_string = JavaStringFromStdString(jni(), error);
jni()->CallVoidMethod(*j_observer_global_, m, j_error_string);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
private:
JNIEnv* jni() {
return AttachCurrentThreadIfNeeded();
}
private:
talk_base::scoped_ptr<ConstraintsWrapper> constraints_;
const ScopedGlobalRef<jobject> j_observer_global_;
const ScopedGlobalRef<jclass> j_observer_class_;
@ -820,6 +823,7 @@ class CreateSdpObserverWrapper
: SdpObserverWrapper(jni, j_observer, constraints) {}
virtual void OnFailure(const std::string& error) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
SdpObserverWrapper::OnFailure(std::string("Create"), error);
}
};
@ -832,6 +836,7 @@ class SetSdpObserverWrapper
: SdpObserverWrapper(jni, j_observer, constraints) {}
virtual void OnFailure(const std::string& error) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
SdpObserverWrapper::OnFailure(std::string("Set"), error);
}
};
@ -855,11 +860,13 @@ class DataChannelObserverWrapper : public DataChannelObserver {
virtual ~DataChannelObserverWrapper() {}
virtual void OnStateChange() OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
jni()->CallVoidMethod(*j_observer_global_, j_on_state_change_mid_);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
virtual void OnMessage(const DataBuffer& buffer) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
jobject byte_buffer =
jni()->NewDirectByteBuffer(const_cast<char*>(buffer.data.data()),
buffer.data.length());
@ -904,11 +911,11 @@ class StatsObserverWrapper : public StatsObserver {
virtual ~StatsObserverWrapper() {}
virtual void OnComplete(const std::vector<StatsReport>& reports) OVERRIDE {
ScopedLocalRef<jobjectArray> j_reports(jni(),
ReportsToJava(jni(), reports));
ScopedLocalRefFrame local_ref_frame(jni());
jobjectArray j_reports = ReportsToJava(jni(), reports);
jmethodID m = GetMethodID(jni(), *j_observer_class_, "onComplete",
"([Lorg/webrtc/StatsReport;)V");
jni()->CallVoidMethod(*j_observer_global_, m, *j_reports);
jni()->CallVoidMethod(*j_observer_global_, m, j_reports);
CHECK_EXCEPTION(jni(), "error during CallVoidMethod");
}
@ -918,17 +925,18 @@ class StatsObserverWrapper : public StatsObserver {
jobjectArray reports_array = jni->NewObjectArray(
reports.size(), *j_stats_report_class_, NULL);
for (int i = 0; i < reports.size(); ++i) {
ScopedLocalRefFrame local_ref_frame(jni);
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);
jstring j_id = JavaStringFromStdString(jni, report.id);
jstring j_type = JavaStringFromStdString(jni, report.type);
jobjectArray j_values = ValuesToJava(jni, report.values);
jobject j_report = 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;
}
@ -937,14 +945,13 @@ class StatsObserverWrapper : public StatsObserver {
jobjectArray j_values = jni->NewObjectArray(
values.size(), *j_value_class_, NULL);
for (int i = 0; i < values.size(); ++i) {
ScopedLocalRefFrame local_ref_frame(jni);
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);
jstring j_name = JavaStringFromStdString(jni, value.name);
jstring j_value = JavaStringFromStdString(jni, value.value);
jobject j_element_value =
jni->NewObject(*j_value_class_, j_value_ctor_, j_name, j_value);
jni->SetObjectArrayElement(j_values, i, j_element_value);
}
return j_values;
}
@ -974,11 +981,13 @@ class VideoRendererWrapper : public VideoRendererInterface {
virtual ~VideoRendererWrapper() {}
virtual void SetSize(int width, int height) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(AttachCurrentThreadIfNeeded());
const bool kNotReserved = false; // What does this param mean??
renderer_->SetSize(width, height, kNotReserved);
}
virtual void RenderFrame(const cricket::VideoFrame* frame) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(AttachCurrentThreadIfNeeded());
renderer_->RenderFrame(frame);
}
@ -1011,40 +1020,41 @@ class JavaVideoRendererWrapper : public VideoRendererInterface {
virtual ~JavaVideoRendererWrapper() {}
virtual void SetSize(int width, int height) OVERRIDE {
ScopedLocalRefFrame local_ref_frame(jni());
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);
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_frame = 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);
jintArray strides = 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(
jni()->ReleaseIntArrayElements(strides, strides_array, 0);
jobjectArray planes = jni()->NewObjectArray(3, *j_byte_buffer_class_, NULL);
jobject y_buffer = 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);
frame->GetYPitch() * frame->GetHeight());
jobject u_buffer = jni()->NewDirectByteBuffer(
const_cast<uint8*>(frame->GetUPlane()), frame->GetChromaSize());
jobject v_buffer = jni()->NewDirectByteBuffer(
const_cast<uint8*>(frame->GetVPlane()), frame->GetChromaSize());
jni()->SetObjectArrayElement(planes, 0, y_buffer);
jni()->SetObjectArrayElement(planes, 1, u_buffer);
jni()->SetObjectArrayElement(planes, 2, v_buffer);
return jni()->NewObject(
*j_frame_class_, j_frame_ctor_id_,
frame->GetWidth(), frame->GetHeight(), *strides, *planes);
frame->GetWidth(), frame->GetHeight(), strides, planes);
}
JNIEnv* jni() {