587 lines
19 KiB
C++
587 lines
19 KiB
C++
/*
|
|
* Copyright (c) 2017 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.
|
|
*/
|
|
|
|
#include "examples/unityplugin/simple_peer_connection.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "absl/memory/memory.h"
|
|
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
|
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
|
#include "api/create_peerconnection_factory.h"
|
|
#include "media/engine/internal_decoder_factory.h"
|
|
#include "media/engine/internal_encoder_factory.h"
|
|
#include "media/engine/multiplex_codec_factory.h"
|
|
#include "modules/audio_device/include/audio_device.h"
|
|
#include "modules/audio_processing/include/audio_processing.h"
|
|
#include "modules/video_capture/video_capture_factory.h"
|
|
#include "pc/video_track_source.h"
|
|
#include "test/vcm_capturer.h"
|
|
|
|
#if defined(WEBRTC_ANDROID)
|
|
#include "examples/unityplugin/class_reference_holder.h"
|
|
#include "modules/utility/include/helpers_android.h"
|
|
#include "sdk/android/src/jni/android_video_track_source.h"
|
|
#include "sdk/android/src/jni/jni_helpers.h"
|
|
#endif
|
|
|
|
// Names used for media stream ids.
|
|
const char kAudioLabel[] = "audio_label";
|
|
const char kVideoLabel[] = "video_label";
|
|
const char kStreamId[] = "stream_id";
|
|
|
|
namespace {
|
|
static int g_peer_count = 0;
|
|
static std::unique_ptr<rtc::Thread> g_worker_thread;
|
|
static std::unique_ptr<rtc::Thread> g_signaling_thread;
|
|
static rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
|
|
g_peer_connection_factory;
|
|
#if defined(WEBRTC_ANDROID)
|
|
// Android case: the video track does not own the capturer, and it
|
|
// relies on the app to dispose the capturer when the peerconnection
|
|
// shuts down.
|
|
static jobject g_camera = nullptr;
|
|
#else
|
|
class CapturerTrackSource : public webrtc::VideoTrackSource {
|
|
public:
|
|
static rtc::scoped_refptr<CapturerTrackSource> Create() {
|
|
const size_t kWidth = 640;
|
|
const size_t kHeight = 480;
|
|
const size_t kFps = 30;
|
|
const size_t kDeviceIndex = 0;
|
|
std::unique_ptr<webrtc::test::VcmCapturer> capturer = absl::WrapUnique(
|
|
webrtc::test::VcmCapturer::Create(kWidth, kHeight, kFps, kDeviceIndex));
|
|
if (!capturer) {
|
|
return nullptr;
|
|
}
|
|
return new rtc::RefCountedObject<CapturerTrackSource>(std::move(capturer));
|
|
}
|
|
|
|
protected:
|
|
explicit CapturerTrackSource(
|
|
std::unique_ptr<webrtc::test::VcmCapturer> capturer)
|
|
: VideoTrackSource(/*remote=*/false), capturer_(std::move(capturer)) {}
|
|
|
|
private:
|
|
rtc::VideoSourceInterface<webrtc::VideoFrame>* source() override {
|
|
return capturer_.get();
|
|
}
|
|
std::unique_ptr<webrtc::test::VcmCapturer> capturer_;
|
|
};
|
|
|
|
#endif
|
|
|
|
std::string GetEnvVarOrDefault(const char* env_var_name,
|
|
const char* default_value) {
|
|
std::string value;
|
|
const char* env_var = getenv(env_var_name);
|
|
if (env_var)
|
|
value = env_var;
|
|
|
|
if (value.empty())
|
|
value = default_value;
|
|
|
|
return value;
|
|
}
|
|
|
|
std::string GetPeerConnectionString() {
|
|
return GetEnvVarOrDefault("WEBRTC_CONNECT", "stun:stun.l.google.com:19302");
|
|
}
|
|
|
|
class DummySetSessionDescriptionObserver
|
|
: public webrtc::SetSessionDescriptionObserver {
|
|
public:
|
|
static DummySetSessionDescriptionObserver* Create() {
|
|
return new rtc::RefCountedObject<DummySetSessionDescriptionObserver>();
|
|
}
|
|
virtual void OnSuccess() { RTC_LOG(INFO) << __FUNCTION__; }
|
|
virtual void OnFailure(webrtc::RTCError error) {
|
|
RTC_LOG(INFO) << __FUNCTION__ << " " << ToString(error.type()) << ": "
|
|
<< error.message();
|
|
}
|
|
|
|
protected:
|
|
DummySetSessionDescriptionObserver() {}
|
|
~DummySetSessionDescriptionObserver() {}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool SimplePeerConnection::InitializePeerConnection(const char** turn_urls,
|
|
const int no_of_urls,
|
|
const char* username,
|
|
const char* credential,
|
|
bool is_receiver) {
|
|
RTC_DCHECK(peer_connection_.get() == nullptr);
|
|
|
|
if (g_peer_connection_factory == nullptr) {
|
|
g_worker_thread = rtc::Thread::Create();
|
|
g_worker_thread->Start();
|
|
g_signaling_thread = rtc::Thread::Create();
|
|
g_signaling_thread->Start();
|
|
|
|
g_peer_connection_factory = webrtc::CreatePeerConnectionFactory(
|
|
g_worker_thread.get(), g_worker_thread.get(), g_signaling_thread.get(),
|
|
nullptr, webrtc::CreateBuiltinAudioEncoderFactory(),
|
|
webrtc::CreateBuiltinAudioDecoderFactory(),
|
|
std::unique_ptr<webrtc::VideoEncoderFactory>(
|
|
new webrtc::MultiplexEncoderFactory(
|
|
std::make_unique<webrtc::InternalEncoderFactory>())),
|
|
std::unique_ptr<webrtc::VideoDecoderFactory>(
|
|
new webrtc::MultiplexDecoderFactory(
|
|
std::make_unique<webrtc::InternalDecoderFactory>())),
|
|
nullptr, nullptr);
|
|
}
|
|
if (!g_peer_connection_factory.get()) {
|
|
DeletePeerConnection();
|
|
return false;
|
|
}
|
|
|
|
g_peer_count++;
|
|
if (!CreatePeerConnection(turn_urls, no_of_urls, username, credential)) {
|
|
DeletePeerConnection();
|
|
return false;
|
|
}
|
|
|
|
mandatory_receive_ = is_receiver;
|
|
return peer_connection_.get() != nullptr;
|
|
}
|
|
|
|
bool SimplePeerConnection::CreatePeerConnection(const char** turn_urls,
|
|
const int no_of_urls,
|
|
const char* username,
|
|
const char* credential) {
|
|
RTC_DCHECK(g_peer_connection_factory.get() != nullptr);
|
|
RTC_DCHECK(peer_connection_.get() == nullptr);
|
|
|
|
local_video_observer_.reset(new VideoObserver());
|
|
remote_video_observer_.reset(new VideoObserver());
|
|
|
|
// Add the turn server.
|
|
if (turn_urls != nullptr) {
|
|
if (no_of_urls > 0) {
|
|
webrtc::PeerConnectionInterface::IceServer turn_server;
|
|
for (int i = 0; i < no_of_urls; i++) {
|
|
std::string url(turn_urls[i]);
|
|
if (url.length() > 0)
|
|
turn_server.urls.push_back(turn_urls[i]);
|
|
}
|
|
|
|
std::string user_name(username);
|
|
if (user_name.length() > 0)
|
|
turn_server.username = username;
|
|
|
|
std::string password(credential);
|
|
if (password.length() > 0)
|
|
turn_server.password = credential;
|
|
|
|
config_.servers.push_back(turn_server);
|
|
}
|
|
}
|
|
|
|
// Add the stun server.
|
|
webrtc::PeerConnectionInterface::IceServer stun_server;
|
|
stun_server.uri = GetPeerConnectionString();
|
|
config_.servers.push_back(stun_server);
|
|
config_.enable_rtp_data_channel = true;
|
|
config_.enable_dtls_srtp = false;
|
|
|
|
peer_connection_ = g_peer_connection_factory->CreatePeerConnection(
|
|
config_, nullptr, nullptr, this);
|
|
|
|
return peer_connection_.get() != nullptr;
|
|
}
|
|
|
|
void SimplePeerConnection::DeletePeerConnection() {
|
|
g_peer_count--;
|
|
|
|
#if defined(WEBRTC_ANDROID)
|
|
if (g_camera) {
|
|
JNIEnv* env = webrtc::jni::GetEnv();
|
|
jclass pc_factory_class =
|
|
unity_plugin::FindClass(env, "org/webrtc/UnityUtility");
|
|
jmethodID stop_camera_method = webrtc::GetStaticMethodID(
|
|
env, pc_factory_class, "StopCamera", "(Lorg/webrtc/VideoCapturer;)V");
|
|
|
|
env->CallStaticVoidMethod(pc_factory_class, stop_camera_method, g_camera);
|
|
CHECK_EXCEPTION(env);
|
|
|
|
g_camera = nullptr;
|
|
}
|
|
#endif
|
|
|
|
CloseDataChannel();
|
|
peer_connection_ = nullptr;
|
|
active_streams_.clear();
|
|
|
|
if (g_peer_count == 0) {
|
|
g_peer_connection_factory = nullptr;
|
|
g_signaling_thread.reset();
|
|
g_worker_thread.reset();
|
|
}
|
|
}
|
|
|
|
bool SimplePeerConnection::CreateOffer() {
|
|
if (!peer_connection_.get())
|
|
return false;
|
|
|
|
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
|
if (mandatory_receive_) {
|
|
options.offer_to_receive_audio = true;
|
|
options.offer_to_receive_video = true;
|
|
}
|
|
peer_connection_->CreateOffer(this, options);
|
|
return true;
|
|
}
|
|
|
|
bool SimplePeerConnection::CreateAnswer() {
|
|
if (!peer_connection_.get())
|
|
return false;
|
|
|
|
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
|
if (mandatory_receive_) {
|
|
options.offer_to_receive_audio = true;
|
|
options.offer_to_receive_video = true;
|
|
}
|
|
peer_connection_->CreateAnswer(this, options);
|
|
return true;
|
|
}
|
|
|
|
void SimplePeerConnection::OnSuccess(
|
|
webrtc::SessionDescriptionInterface* desc) {
|
|
peer_connection_->SetLocalDescription(
|
|
DummySetSessionDescriptionObserver::Create(), desc);
|
|
|
|
std::string sdp;
|
|
desc->ToString(&sdp);
|
|
|
|
if (OnLocalSdpReady)
|
|
OnLocalSdpReady(desc->type().c_str(), sdp.c_str());
|
|
}
|
|
|
|
void SimplePeerConnection::OnFailure(webrtc::RTCError error) {
|
|
RTC_LOG(LERROR) << ToString(error.type()) << ": " << error.message();
|
|
|
|
// TODO(hta): include error.type in the message
|
|
if (OnFailureMessage)
|
|
OnFailureMessage(error.message());
|
|
}
|
|
|
|
void SimplePeerConnection::OnIceCandidate(
|
|
const webrtc::IceCandidateInterface* candidate) {
|
|
RTC_LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
|
|
|
|
std::string sdp;
|
|
if (!candidate->ToString(&sdp)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to serialize candidate";
|
|
return;
|
|
}
|
|
|
|
if (OnIceCandiateReady)
|
|
OnIceCandiateReady(sdp.c_str(), candidate->sdp_mline_index(),
|
|
candidate->sdp_mid().c_str());
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnLocalI420FrameReady(
|
|
I420FRAMEREADY_CALLBACK callback) {
|
|
if (local_video_observer_)
|
|
local_video_observer_->SetVideoCallback(callback);
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnRemoteI420FrameReady(
|
|
I420FRAMEREADY_CALLBACK callback) {
|
|
if (remote_video_observer_)
|
|
remote_video_observer_->SetVideoCallback(callback);
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnLocalDataChannelReady(
|
|
LOCALDATACHANNELREADY_CALLBACK callback) {
|
|
OnLocalDataChannelReady = callback;
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnDataFromDataChannelReady(
|
|
DATAFROMEDATECHANNELREADY_CALLBACK callback) {
|
|
OnDataFromDataChannelReady = callback;
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnFailure(FAILURE_CALLBACK callback) {
|
|
OnFailureMessage = callback;
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnAudioBusReady(
|
|
AUDIOBUSREADY_CALLBACK callback) {
|
|
OnAudioReady = callback;
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnLocalSdpReadytoSend(
|
|
LOCALSDPREADYTOSEND_CALLBACK callback) {
|
|
OnLocalSdpReady = callback;
|
|
}
|
|
|
|
void SimplePeerConnection::RegisterOnIceCandiateReadytoSend(
|
|
ICECANDIDATEREADYTOSEND_CALLBACK callback) {
|
|
OnIceCandiateReady = callback;
|
|
}
|
|
|
|
bool SimplePeerConnection::SetRemoteDescription(const char* type,
|
|
const char* sdp) {
|
|
if (!peer_connection_)
|
|
return false;
|
|
|
|
std::string remote_desc(sdp);
|
|
std::string sdp_type(type);
|
|
webrtc::SdpParseError error;
|
|
webrtc::SessionDescriptionInterface* session_description(
|
|
webrtc::CreateSessionDescription(sdp_type, remote_desc, &error));
|
|
if (!session_description) {
|
|
RTC_LOG(WARNING) << "Can't parse received session description message. "
|
|
"SdpParseError was: "
|
|
<< error.description;
|
|
return false;
|
|
}
|
|
RTC_LOG(INFO) << " Received session description :" << remote_desc;
|
|
peer_connection_->SetRemoteDescription(
|
|
DummySetSessionDescriptionObserver::Create(), session_description);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SimplePeerConnection::AddIceCandidate(const char* candidate,
|
|
const int sdp_mlineindex,
|
|
const char* sdp_mid) {
|
|
if (!peer_connection_)
|
|
return false;
|
|
|
|
webrtc::SdpParseError error;
|
|
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate(
|
|
webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate, &error));
|
|
if (!ice_candidate.get()) {
|
|
RTC_LOG(WARNING) << "Can't parse received candidate message. "
|
|
"SdpParseError was: "
|
|
<< error.description;
|
|
return false;
|
|
}
|
|
if (!peer_connection_->AddIceCandidate(ice_candidate.get())) {
|
|
RTC_LOG(WARNING) << "Failed to apply the received candidate";
|
|
return false;
|
|
}
|
|
RTC_LOG(INFO) << " Received candidate :" << candidate;
|
|
return true;
|
|
}
|
|
|
|
void SimplePeerConnection::SetAudioControl(bool is_mute, bool is_record) {
|
|
is_mute_audio_ = is_mute;
|
|
is_record_audio_ = is_record;
|
|
|
|
SetAudioControl();
|
|
}
|
|
|
|
void SimplePeerConnection::SetAudioControl() {
|
|
if (!remote_stream_)
|
|
return;
|
|
webrtc::AudioTrackVector tracks = remote_stream_->GetAudioTracks();
|
|
if (tracks.empty())
|
|
return;
|
|
|
|
webrtc::AudioTrackInterface* audio_track = tracks[0];
|
|
std::string id = audio_track->id();
|
|
if (is_record_audio_)
|
|
audio_track->AddSink(this);
|
|
else
|
|
audio_track->RemoveSink(this);
|
|
|
|
for (auto& track : tracks) {
|
|
if (is_mute_audio_)
|
|
track->set_enabled(false);
|
|
else
|
|
track->set_enabled(true);
|
|
}
|
|
}
|
|
|
|
void SimplePeerConnection::OnAddStream(
|
|
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
|
|
RTC_LOG(INFO) << __FUNCTION__ << " " << stream->id();
|
|
remote_stream_ = stream;
|
|
if (remote_video_observer_ && !remote_stream_->GetVideoTracks().empty()) {
|
|
remote_stream_->GetVideoTracks()[0]->AddOrUpdateSink(
|
|
remote_video_observer_.get(), rtc::VideoSinkWants());
|
|
}
|
|
SetAudioControl();
|
|
}
|
|
|
|
void SimplePeerConnection::AddStreams(bool audio_only) {
|
|
if (active_streams_.find(kStreamId) != active_streams_.end())
|
|
return; // Already added.
|
|
|
|
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
|
|
g_peer_connection_factory->CreateLocalMediaStream(kStreamId);
|
|
|
|
rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
|
|
g_peer_connection_factory->CreateAudioTrack(
|
|
kAudioLabel, g_peer_connection_factory->CreateAudioSource(
|
|
cricket::AudioOptions())));
|
|
std::string id = audio_track->id();
|
|
stream->AddTrack(audio_track);
|
|
|
|
if (!audio_only) {
|
|
#if defined(WEBRTC_ANDROID)
|
|
JNIEnv* env = webrtc::jni::GetEnv();
|
|
jclass pc_factory_class =
|
|
unity_plugin::FindClass(env, "org/webrtc/UnityUtility");
|
|
jmethodID load_texture_helper_method = webrtc::GetStaticMethodID(
|
|
env, pc_factory_class, "LoadSurfaceTextureHelper",
|
|
"()Lorg/webrtc/SurfaceTextureHelper;");
|
|
jobject texture_helper = env->CallStaticObjectMethod(
|
|
pc_factory_class, load_texture_helper_method);
|
|
CHECK_EXCEPTION(env);
|
|
RTC_DCHECK(texture_helper != nullptr)
|
|
<< "Cannot get the Surface Texture Helper.";
|
|
|
|
rtc::scoped_refptr<webrtc::jni::AndroidVideoTrackSource> source(
|
|
new rtc::RefCountedObject<webrtc::jni::AndroidVideoTrackSource>(
|
|
g_signaling_thread.get(), env, /* is_screencast= */ false,
|
|
/* align_timestamps= */ true));
|
|
|
|
// link with VideoCapturer (Camera);
|
|
jmethodID link_camera_method = webrtc::GetStaticMethodID(
|
|
env, pc_factory_class, "LinkCamera",
|
|
"(JLorg/webrtc/SurfaceTextureHelper;)Lorg/webrtc/VideoCapturer;");
|
|
jobject camera_tmp =
|
|
env->CallStaticObjectMethod(pc_factory_class, link_camera_method,
|
|
(jlong)source.get(), texture_helper);
|
|
CHECK_EXCEPTION(env);
|
|
g_camera = (jobject)env->NewGlobalRef(camera_tmp);
|
|
|
|
rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(
|
|
g_peer_connection_factory->CreateVideoTrack(kVideoLabel,
|
|
source.release()));
|
|
stream->AddTrack(video_track);
|
|
#else
|
|
rtc::scoped_refptr<CapturerTrackSource> video_device =
|
|
CapturerTrackSource::Create();
|
|
if (video_device) {
|
|
rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(
|
|
g_peer_connection_factory->CreateVideoTrack(kVideoLabel,
|
|
video_device));
|
|
|
|
stream->AddTrack(video_track);
|
|
}
|
|
#endif
|
|
if (local_video_observer_ && !stream->GetVideoTracks().empty()) {
|
|
stream->GetVideoTracks()[0]->AddOrUpdateSink(local_video_observer_.get(),
|
|
rtc::VideoSinkWants());
|
|
}
|
|
}
|
|
|
|
if (!peer_connection_->AddStream(stream)) {
|
|
RTC_LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
|
|
}
|
|
|
|
typedef std::pair<std::string,
|
|
rtc::scoped_refptr<webrtc::MediaStreamInterface>>
|
|
MediaStreamPair;
|
|
active_streams_.insert(MediaStreamPair(stream->id(), stream));
|
|
}
|
|
|
|
bool SimplePeerConnection::CreateDataChannel() {
|
|
struct webrtc::DataChannelInit init;
|
|
init.ordered = true;
|
|
init.reliable = true;
|
|
data_channel_ = peer_connection_->CreateDataChannel("Hello", &init);
|
|
if (data_channel_.get()) {
|
|
data_channel_->RegisterObserver(this);
|
|
RTC_LOG(LS_INFO) << "Succeeds to create data channel";
|
|
return true;
|
|
} else {
|
|
RTC_LOG(LS_INFO) << "Fails to create data channel";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void SimplePeerConnection::CloseDataChannel() {
|
|
if (data_channel_.get()) {
|
|
data_channel_->UnregisterObserver();
|
|
data_channel_->Close();
|
|
}
|
|
data_channel_ = nullptr;
|
|
}
|
|
|
|
bool SimplePeerConnection::SendDataViaDataChannel(const std::string& data) {
|
|
if (!data_channel_.get()) {
|
|
RTC_LOG(LS_INFO) << "Data channel is not established";
|
|
return false;
|
|
}
|
|
webrtc::DataBuffer buffer(data);
|
|
data_channel_->Send(buffer);
|
|
return true;
|
|
}
|
|
|
|
// Peerconnection observer
|
|
void SimplePeerConnection::OnDataChannel(
|
|
rtc::scoped_refptr<webrtc::DataChannelInterface> channel) {
|
|
channel->RegisterObserver(this);
|
|
}
|
|
|
|
void SimplePeerConnection::OnStateChange() {
|
|
if (data_channel_) {
|
|
webrtc::DataChannelInterface::DataState state = data_channel_->state();
|
|
if (state == webrtc::DataChannelInterface::kOpen) {
|
|
if (OnLocalDataChannelReady)
|
|
OnLocalDataChannelReady();
|
|
RTC_LOG(LS_INFO) << "Data channel is open";
|
|
}
|
|
}
|
|
}
|
|
|
|
// A data buffer was successfully received.
|
|
void SimplePeerConnection::OnMessage(const webrtc::DataBuffer& buffer) {
|
|
size_t size = buffer.data.size();
|
|
char* msg = new char[size + 1];
|
|
memcpy(msg, buffer.data.data(), size);
|
|
msg[size] = 0;
|
|
if (OnDataFromDataChannelReady)
|
|
OnDataFromDataChannelReady(msg);
|
|
delete[] msg;
|
|
}
|
|
|
|
// AudioTrackSinkInterface implementation.
|
|
void SimplePeerConnection::OnData(const void* audio_data,
|
|
int bits_per_sample,
|
|
int sample_rate,
|
|
size_t number_of_channels,
|
|
size_t number_of_frames) {
|
|
if (OnAudioReady)
|
|
OnAudioReady(audio_data, bits_per_sample, sample_rate,
|
|
static_cast<int>(number_of_channels),
|
|
static_cast<int>(number_of_frames));
|
|
}
|
|
|
|
std::vector<uint32_t> SimplePeerConnection::GetRemoteAudioTrackSsrcs() {
|
|
std::vector<rtc::scoped_refptr<webrtc::RtpReceiverInterface>> receivers =
|
|
peer_connection_->GetReceivers();
|
|
|
|
std::vector<uint32_t> ssrcs;
|
|
for (const auto& receiver : receivers) {
|
|
if (receiver->media_type() != cricket::MEDIA_TYPE_AUDIO)
|
|
continue;
|
|
|
|
std::vector<webrtc::RtpEncodingParameters> params =
|
|
receiver->GetParameters().encodings;
|
|
|
|
for (const auto& param : params) {
|
|
uint32_t ssrc = param.ssrc.value_or(0);
|
|
if (ssrc > 0)
|
|
ssrcs.push_back(ssrc);
|
|
}
|
|
}
|
|
|
|
return ssrcs;
|
|
}
|