Update Java MediaStream when native stream's set of tracks changes.

This will handle the scenario where, for example, the initial
offer/answer only negotiates audio, and video is added later (to the
same stream). Previously, there was absolutely no way to get a handle to
the new track without hacking the SDP. Now, the stream will be updated
after setRemoteDescription finishes.

Bug: webrtc:5677
Change-Id: Iea31bb7744da6b82afdaf44c8f74d721298a9474
Reviewed-on: https://webrtc-review.googlesource.com/6261
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Commit-Queue: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20228}
This commit is contained in:
Taylor Brandstetter
2017-10-09 17:16:54 -07:00
committed by Commit Bot
parent 933d8b07ea
commit ebe36efad7
6 changed files with 339 additions and 50 deletions

View File

@ -78,6 +78,7 @@ ClassReferenceHolder::ClassReferenceHolder(JNIEnv* jni) {
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$VideoCodecType");
LoadClass(jni, "org/webrtc/MediaSource$State");
LoadClass(jni, "org/webrtc/MediaStream");
LoadClass(jni, "org/webrtc/MediaStreamTrack");
LoadClass(jni, "org/webrtc/MediaStreamTrack$MediaType");
LoadClass(jni, "org/webrtc/MediaStreamTrack$State");
LoadClass(jni, "org/webrtc/NetworkMonitor");

View File

@ -379,6 +379,7 @@ Iterable::Iterator::Iterator(JNIEnv* jni, jobject iterable) : jni_(jni) {
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;");
remove_id_ = GetMethodID(jni, iterator_class, "remove", "()V");
// Start at the first element in the collection.
++(*this);
@ -414,6 +415,11 @@ Iterable::Iterator& Iterable::Iterator::operator++() {
return *this;
}
void Iterable::Iterator::Remove() {
jni_->CallVoidMethod(iterator_, remove_id_);
CHECK_EXCEPTION(jni_) << "error during CallVoidMethod";
}
// 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.

View File

@ -184,6 +184,10 @@ class Iterable {
// Advances the iterator one step.
Iterator& operator++();
// Removes the element the iterator is pointing to. Must still advance the
// iterator afterwards.
void Remove();
// 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
@ -200,6 +204,7 @@ class Iterable {
jobject value_ = nullptr;
jmethodID has_next_id_ = nullptr;
jmethodID next_id_ = nullptr;
jmethodID remove_id_ = nullptr;
rtc::ThreadChecker thread_checker_;
RTC_DISALLOW_COPY_AND_ASSIGN(Iterator);

View File

@ -10,8 +10,10 @@
#include "sdk/android/src/jni/pc/peerconnectionobserver_jni.h"
#include <pc/mediastreamobserver.h>
#include <string>
#include "rtc_base/ptr_util.h"
#include "sdk/android/src/jni/classreferenceholder.h"
#include "sdk/android/src/jni/pc/java_native_conversion.h"
@ -31,6 +33,13 @@ PeerConnectionObserverJni::PeerConnectionObserverJni(JNIEnv* jni,
j_media_stream_class_(jni, FindClass(jni, "org/webrtc/MediaStream")),
j_media_stream_ctor_(
GetMethodID(jni, *j_media_stream_class_, "<init>", "(J)V")),
j_media_stream_track_class_(
jni,
FindClass(jni, "org/webrtc/MediaStreamTrack")),
j_track_dispose_id_(
GetMethodID(jni, *j_media_stream_track_class_, "dispose", "()V")),
j_native_track_id_(
GetFieldID(jni, *j_media_stream_track_class_, "nativeTrack", "J")),
j_audio_track_class_(jni, FindClass(jni, "org/webrtc/AudioTrack")),
j_audio_track_ctor_(
GetMethodID(jni, *j_audio_track_class_, "<init>", "(J)V")),
@ -133,47 +142,132 @@ void PeerConnectionObserverJni::OnAddStream(
jobject j_stream = GetOrCreateJavaStream(stream);
for (const auto& track : stream->GetAudioTracks()) {
jstring id = JavaStringFromStdString(jni(), track->id());
// Java AudioTrack holds one reference. Corresponding Release() is in
// MediaStreamTrack_free, triggered by AudioTrack.dispose().
track->AddRef();
jobject j_track =
jni()->NewObject(*j_audio_track_class_, j_audio_track_ctor_,
reinterpret_cast<jlong>(track.get()), id);
CHECK_EXCEPTION(jni()) << "error during NewObject";
jfieldID audio_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "audioTracks", "Ljava/util/LinkedList;");
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);
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
RTC_CHECK(added);
AddNativeAudioTrackToJavaStream(track, j_stream);
}
for (const auto& track : stream->GetVideoTracks()) {
jstring id = JavaStringFromStdString(jni(), track->id());
// Java VideoTrack holds one reference. Corresponding Release() is in
// MediaStreamTrack_free, triggered by VideoTrack.dispose().
track->AddRef();
jobject j_track =
jni()->NewObject(*j_video_track_class_, j_video_track_ctor_,
reinterpret_cast<jlong>(track.get()), id);
CHECK_EXCEPTION(jni()) << "error during NewObject";
jfieldID video_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "videoTracks", "Ljava/util/LinkedList;");
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);
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
RTC_CHECK(added);
AddNativeVideoTrackToJavaStream(track, j_stream);
}
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";
// Create an observer to update the Java stream when the native stream's set
// of tracks changes.
auto observer = rtc::MakeUnique<MediaStreamObserver>(stream);
observer->SignalAudioTrackRemoved.connect(
this, &PeerConnectionObserverJni::OnAudioTrackRemovedFromStream);
observer->SignalVideoTrackRemoved.connect(
this, &PeerConnectionObserverJni::OnVideoTrackRemovedFromStream);
observer->SignalAudioTrackAdded.connect(
this, &PeerConnectionObserverJni::OnAudioTrackAddedToStream);
observer->SignalVideoTrackAdded.connect(
this, &PeerConnectionObserverJni::OnVideoTrackAddedToStream);
stream_observers_.push_back(std::move(observer));
}
void PeerConnectionObserverJni::AddNativeAudioTrackToJavaStream(
rtc::scoped_refptr<AudioTrackInterface> track,
jobject j_stream) {
jstring id = JavaStringFromStdString(jni(), track->id());
// Java AudioTrack holds one reference. Corresponding Release() is in
// MediaStreamTrack_free, triggered by AudioTrack.dispose().
track->AddRef();
jobject j_track = jni()->NewObject(*j_audio_track_class_, j_audio_track_ctor_,
reinterpret_cast<jlong>(track.get()), id);
CHECK_EXCEPTION(jni()) << "error during NewObject";
// Now add to the audioTracks linked list.
jfieldID audio_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "audioTracks", "Ljava/util/LinkedList;");
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);
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
RTC_CHECK(added);
}
void PeerConnectionObserverJni::AddNativeVideoTrackToJavaStream(
rtc::scoped_refptr<VideoTrackInterface> track,
jobject j_stream) {
jstring id = JavaStringFromStdString(jni(), track->id());
// Java VideoTrack holds one reference. Corresponding Release() is in
// MediaStreamTrack_free, triggered by VideoTrack.dispose().
track->AddRef();
jobject j_track = jni()->NewObject(*j_video_track_class_, j_video_track_ctor_,
reinterpret_cast<jlong>(track.get()), id);
CHECK_EXCEPTION(jni()) << "error during NewObject";
// Now add to the videoTracks linked list.
jfieldID video_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "videoTracks", "Ljava/util/LinkedList;");
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);
CHECK_EXCEPTION(jni()) << "error during CallBooleanMethod";
RTC_CHECK(added);
}
void PeerConnectionObserverJni::RemoveAndDisposeNativeTrackFromJavaTrackList(
MediaStreamTrackInterface* track,
jobject j_tracks) {
Iterable iterable_tracks(jni(), j_tracks);
for (auto it = iterable_tracks.begin(); it != iterable_tracks.end(); ++it) {
MediaStreamTrackInterface* native_track =
reinterpret_cast<MediaStreamTrackInterface*>(
jni()->GetLongField(*it, j_native_track_id_));
CHECK_EXCEPTION(jni()) << "error during GetLongField";
if (native_track == track) {
jni()->CallVoidMethod(*it, j_track_dispose_id_);
it.Remove();
return;
}
}
// If we reached this point, we didn't find the track, which means we're
// getting a "track removed" callback but the Java stream doesn't have a
// corresponding track, which indicates a bug somewhere.
RTC_NOTREACHED();
}
void PeerConnectionObserverJni::OnAudioTrackAddedToStream(
AudioTrackInterface* track,
MediaStreamInterface* stream) {
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_stream = GetOrCreateJavaStream(stream);
AddNativeAudioTrackToJavaStream(track, j_stream);
}
void PeerConnectionObserverJni::OnVideoTrackAddedToStream(
VideoTrackInterface* track,
MediaStreamInterface* stream) {
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_stream = GetOrCreateJavaStream(stream);
AddNativeVideoTrackToJavaStream(track, j_stream);
}
void PeerConnectionObserverJni::OnAudioTrackRemovedFromStream(
AudioTrackInterface* track,
MediaStreamInterface* stream) {
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_stream = GetOrCreateJavaStream(stream);
jfieldID audio_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "audioTracks", "Ljava/util/LinkedList;");
jobject audio_tracks = GetObjectField(jni(), j_stream, audio_tracks_id);
RemoveAndDisposeNativeTrackFromJavaTrackList(track, audio_tracks);
}
void PeerConnectionObserverJni::OnVideoTrackRemovedFromStream(
VideoTrackInterface* track,
MediaStreamInterface* stream) {
ScopedLocalRefFrame local_ref_frame(jni());
jobject j_stream = GetOrCreateJavaStream(stream);
jfieldID video_tracks_id = GetFieldID(
jni(), *j_media_stream_class_, "videoTracks", "Ljava/util/LinkedList;");
jobject video_tracks = GetObjectField(jni(), j_stream, video_tracks_id);
RemoveAndDisposeNativeTrackFromJavaTrackList(track, video_tracks);
}
void PeerConnectionObserverJni::OnRemoveStream(
@ -187,6 +281,7 @@ void PeerConnectionObserverJni::OnRemoveStream(
"(Lorg/webrtc/MediaStream;)V");
jni()->CallVoidMethod(*j_observer_global_, m, j_stream);
CHECK_EXCEPTION(jni()) << "error during CallVoidMethod";
// Release the refptr reference so that DisposeRemoteStream can assert
// it removes the final reference.
stream = nullptr;
@ -250,7 +345,18 @@ void PeerConnectionObserverJni::SetConstraints(
void PeerConnectionObserverJni::DisposeRemoteStream(
const NativeToJavaStreamsMap::iterator& it) {
MediaStreamInterface* stream = it->first;
jobject j_stream = it->second;
// Remove the observer first, so it doesn't react to events during deletion.
stream_observers_.erase(
std::remove_if(
stream_observers_.begin(), stream_observers_.end(),
[stream](const std::unique_ptr<MediaStreamObserver>& observer) {
return observer->stream() == stream;
}),
stream_observers_.end());
remote_streams_.erase(it);
jni()->CallVoidMethod(
j_stream, GetMethodID(jni(), *j_media_stream_class_, "dispose", "()V"));
@ -277,7 +383,6 @@ jobject PeerConnectionObserverJni::GetOrCreateJavaStream(
if (it != remote_streams_.end()) {
return it->second;
}
// Java MediaStream holds one reference. Corresponding Release() is in
// MediaStream_free, triggered by MediaStream.dispose().
stream->AddRef();

View File

@ -11,6 +11,7 @@
#ifndef SDK_ANDROID_SRC_JNI_PC_PEERCONNECTIONOBSERVER_JNI_H_
#define SDK_ANDROID_SRC_JNI_PC_PEERCONNECTIONOBSERVER_JNI_H_
#include <pc/mediastreamobserver.h>
#include <map>
#include <memory>
#include <vector>
@ -25,7 +26,8 @@ namespace jni {
// 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 PeerConnectionObserverJni : public PeerConnectionObserver {
class PeerConnectionObserverJni : public PeerConnectionObserver,
public sigslot::has_slots<> {
public:
PeerConnectionObserverJni(JNIEnv* jni, jobject j_observer);
virtual ~PeerConnectionObserverJni();
@ -56,6 +58,10 @@ class PeerConnectionObserverJni : public PeerConnectionObserver {
private:
typedef std::map<MediaStreamInterface*, jobject> NativeToJavaStreamsMap;
typedef std::map<RtpReceiverInterface*, jobject> NativeToJavaRtpReceiverMap;
typedef std::map<MediaStreamTrackInterface*, jobject>
NativeToJavaMediaTrackMap;
typedef std::map<MediaStreamTrackInterface*, RtpReceiverInterface*>
NativeMediaStreamTrackToNativeRtpReceiver;
void DisposeRemoteStream(const NativeToJavaStreamsMap::iterator& it);
void DisposeRtpReceiver(const NativeToJavaRtpReceiverMap::iterator& it);
@ -70,10 +76,44 @@ class PeerConnectionObserverJni : public PeerConnectionObserver {
JNIEnv* jni,
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& streams);
// The three methods below must be called from within a local ref
// frame (e.g., using ScopedLocalRefFrame), otherwise they will
// leak references.
//
// Create a Java track object to wrap |track|, and add it to |j_stream|.
void AddNativeAudioTrackToJavaStream(
rtc::scoped_refptr<AudioTrackInterface> track,
jobject j_stream);
void AddNativeVideoTrackToJavaStream(
rtc::scoped_refptr<VideoTrackInterface> track,
jobject j_stream);
// Remove and dispose the Java MediaStreamTrack object that wraps |track|,
// given |j_tracks| which is a linked list of tracks (either the videoTracks
// or audioTracks member of MediaStream).
//
// DCHECKs if the track isn't found.
void RemoveAndDisposeNativeTrackFromJavaTrackList(
MediaStreamTrackInterface* track,
jobject j_tracks);
// Callbacks invoked when a native stream changes, and the Java stream needs
// to be updated; MediaStreamObserver is used to make this simpler.
void OnAudioTrackAddedToStream(AudioTrackInterface* track,
MediaStreamInterface* stream);
void OnVideoTrackAddedToStream(VideoTrackInterface* track,
MediaStreamInterface* stream);
void OnAudioTrackRemovedFromStream(AudioTrackInterface* track,
MediaStreamInterface* stream);
void OnVideoTrackRemovedFromStream(VideoTrackInterface* track,
MediaStreamInterface* stream);
const ScopedGlobalRef<jobject> j_observer_global_;
const ScopedGlobalRef<jclass> j_observer_class_;
const ScopedGlobalRef<jclass> j_media_stream_class_;
const jmethodID j_media_stream_ctor_;
const ScopedGlobalRef<jclass> j_media_stream_track_class_;
const jmethodID j_track_dispose_id_;
const jfieldID j_native_track_id_;
const ScopedGlobalRef<jclass> j_audio_track_class_;
const jmethodID j_audio_track_ctor_;
const ScopedGlobalRef<jclass> j_video_track_class_;
@ -82,11 +122,13 @@ class PeerConnectionObserverJni : public PeerConnectionObserver {
const jmethodID j_data_channel_ctor_;
const ScopedGlobalRef<jclass> j_rtp_receiver_class_;
const jmethodID j_rtp_receiver_ctor_;
// C++ -> Java remote streams. The stored jobects are global refs and must be
// manually deleted upon removal. Use DisposeRemoteStream().
NativeToJavaStreamsMap remote_streams_;
NativeToJavaRtpReceiverMap rtp_receivers_;
std::unique_ptr<MediaConstraintsJni> constraints_;
std::vector<std::unique_ptr<webrtc::MediaStreamObserver>> stream_observers_;
};
} // namespace jni