xref: /aosp_15_r20/external/webrtc/examples/androidvoip/jni/android_voip_client.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright 2020 The WebRTC Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "examples/androidvoip/jni/android_voip_client.h"
12 
13 #include <errno.h>
14 #include <sys/socket.h>
15 #include <algorithm>
16 #include <map>
17 #include <memory>
18 #include <unordered_map>
19 #include <unordered_set>
20 #include <utility>
21 #include <vector>
22 
23 #include "absl/memory/memory.h"
24 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
25 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
26 #include "api/task_queue/default_task_queue_factory.h"
27 #include "api/voip/voip_codec.h"
28 #include "api/voip/voip_engine_factory.h"
29 #include "api/voip/voip_network.h"
30 #include "examples/androidvoip/generated_jni/VoipClient_jni.h"
31 #include "rtc_base/logging.h"
32 #include "rtc_base/network.h"
33 #include "rtc_base/socket_server.h"
34 #include "sdk/android/native_api/audio_device_module/audio_device_android.h"
35 #include "sdk/android/native_api/jni/java_types.h"
36 #include "sdk/android/native_api/jni/jvm.h"
37 #include "sdk/android/native_api/jni/scoped_java_ref.h"
38 
39 namespace {
40 
41 #define RUN_ON_VOIP_THREAD(method, ...)                              \
42   if (!voip_thread_->IsCurrent()) {                                  \
43     voip_thread_->PostTask(                                          \
44         std::bind(&AndroidVoipClient::method, this, ##__VA_ARGS__)); \
45     return;                                                          \
46   }                                                                  \
47   RTC_DCHECK_RUN_ON(voip_thread_.get());
48 
49 // Connects a UDP socket to a public address and returns the local
50 // address associated with it. Since it binds to the "any" address
51 // internally, it returns the default local address on a multi-homed
52 // endpoint. Implementation copied from
53 // BasicNetworkManager::QueryDefaultLocalAddress.
QueryDefaultLocalAddress(int family)54 rtc::IPAddress QueryDefaultLocalAddress(int family) {
55   const char kPublicIPv4Host[] = "8.8.8.8";
56   const char kPublicIPv6Host[] = "2001:4860:4860::8888";
57   const int kPublicPort = 53;
58   std::unique_ptr<rtc::Thread> thread = rtc::Thread::CreateWithSocketServer();
59 
60   RTC_DCHECK(thread->socketserver() != nullptr);
61   RTC_DCHECK(family == AF_INET || family == AF_INET6);
62 
63   std::unique_ptr<rtc::Socket> socket(
64       thread->socketserver()->CreateSocket(family, SOCK_DGRAM));
65   if (!socket) {
66     RTC_LOG_ERR(LS_ERROR) << "Socket creation failed";
67     return rtc::IPAddress();
68   }
69 
70   auto host = family == AF_INET ? kPublicIPv4Host : kPublicIPv6Host;
71   if (socket->Connect(rtc::SocketAddress(host, kPublicPort)) < 0) {
72     if (socket->GetError() != ENETUNREACH &&
73         socket->GetError() != EHOSTUNREACH) {
74       RTC_LOG(LS_INFO) << "Connect failed with " << socket->GetError();
75     }
76     return rtc::IPAddress();
77   }
78   return socket->GetLocalAddress().ipaddr();
79 }
80 
81 // Assigned payload type for supported built-in codecs. PCMU, PCMA,
82 // and G722 have set payload types. Whereas opus, ISAC, and ILBC
83 // have dynamic payload types.
84 enum class PayloadType : int {
85   kPcmu = 0,
86   kPcma = 8,
87   kG722 = 9,
88   kOpus = 96,
89   kIsac = 97,
90   kIlbc = 98,
91 };
92 
93 // Returns the payload type corresponding to codec_name. Only
94 // supports the built-in codecs.
GetPayloadType(const std::string & codec_name)95 int GetPayloadType(const std::string& codec_name) {
96   RTC_DCHECK(codec_name == "PCMU" || codec_name == "PCMA" ||
97              codec_name == "G722" || codec_name == "opus" ||
98              codec_name == "ISAC" || codec_name == "ILBC");
99 
100   if (codec_name == "PCMU") {
101     return static_cast<int>(PayloadType::kPcmu);
102   } else if (codec_name == "PCMA") {
103     return static_cast<int>(PayloadType::kPcma);
104   } else if (codec_name == "G722") {
105     return static_cast<int>(PayloadType::kG722);
106   } else if (codec_name == "opus") {
107     return static_cast<int>(PayloadType::kOpus);
108   } else if (codec_name == "ISAC") {
109     return static_cast<int>(PayloadType::kIsac);
110   } else if (codec_name == "ILBC") {
111     return static_cast<int>(PayloadType::kIlbc);
112   }
113 
114   RTC_DCHECK_NOTREACHED();
115   return -1;
116 }
117 
118 }  // namespace
119 
120 namespace webrtc_examples {
121 
Init(JNIEnv * env,const webrtc::JavaParamRef<jobject> & application_context)122 void AndroidVoipClient::Init(
123     JNIEnv* env,
124     const webrtc::JavaParamRef<jobject>& application_context) {
125   webrtc::VoipEngineConfig config;
126   config.encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
127   config.decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory();
128   config.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
129   config.audio_device_module =
130       webrtc::CreateJavaAudioDeviceModule(env, application_context.obj());
131   config.audio_processing = webrtc::AudioProcessingBuilder().Create();
132 
133   voip_thread_->Start();
134 
135   // Due to consistent thread requirement on
136   // modules/audio_device/android/audio_device_template.h,
137   // code is invoked in the context of voip_thread_.
138   voip_thread_->BlockingCall([this, &config] {
139     RTC_DCHECK_RUN_ON(voip_thread_.get());
140 
141     supported_codecs_ = config.encoder_factory->GetSupportedEncoders();
142     env_ = webrtc::AttachCurrentThreadIfNeeded();
143     voip_engine_ = webrtc::CreateVoipEngine(std::move(config));
144   });
145 }
146 
~AndroidVoipClient()147 AndroidVoipClient::~AndroidVoipClient() {
148   voip_thread_->BlockingCall([this] {
149     RTC_DCHECK_RUN_ON(voip_thread_.get());
150 
151     JavaVM* jvm = nullptr;
152     env_->GetJavaVM(&jvm);
153     if (!jvm) {
154       RTC_LOG(LS_ERROR) << "Failed to retrieve JVM";
155       return;
156     }
157     jint res = jvm->DetachCurrentThread();
158     if (res != JNI_OK) {
159       RTC_LOG(LS_ERROR) << "DetachCurrentThread failed: " << res;
160     }
161   });
162 
163   voip_thread_->Stop();
164 }
165 
Create(JNIEnv * env,const webrtc::JavaParamRef<jobject> & application_context,const webrtc::JavaParamRef<jobject> & j_voip_client)166 AndroidVoipClient* AndroidVoipClient::Create(
167     JNIEnv* env,
168     const webrtc::JavaParamRef<jobject>& application_context,
169     const webrtc::JavaParamRef<jobject>& j_voip_client) {
170   // Using `new` to access a non-public constructor.
171   auto voip_client =
172       absl::WrapUnique(new AndroidVoipClient(env, j_voip_client));
173   voip_client->Init(env, application_context);
174   return voip_client.release();
175 }
176 
GetSupportedCodecs(JNIEnv * env)177 void AndroidVoipClient::GetSupportedCodecs(JNIEnv* env) {
178   RUN_ON_VOIP_THREAD(GetSupportedCodecs, env);
179 
180   std::vector<std::string> names;
181   for (const webrtc::AudioCodecSpec& spec : supported_codecs_) {
182     names.push_back(spec.format.name);
183   }
184   webrtc::ScopedJavaLocalRef<jstring> (*convert_function)(
185       JNIEnv*, const std::string&) = &webrtc::NativeToJavaString;
186   Java_VoipClient_onGetSupportedCodecsCompleted(
187       env_, j_voip_client_, NativeToJavaList(env_, names, convert_function));
188 }
189 
GetLocalIPAddress(JNIEnv * env)190 void AndroidVoipClient::GetLocalIPAddress(JNIEnv* env) {
191   RUN_ON_VOIP_THREAD(GetLocalIPAddress, env);
192 
193   std::string local_ip_address;
194   rtc::IPAddress ipv4_address = QueryDefaultLocalAddress(AF_INET);
195   if (!ipv4_address.IsNil()) {
196     local_ip_address = ipv4_address.ToString();
197   } else {
198     rtc::IPAddress ipv6_address = QueryDefaultLocalAddress(AF_INET6);
199     if (!ipv6_address.IsNil()) {
200       local_ip_address = ipv6_address.ToString();
201     }
202   }
203   Java_VoipClient_onGetLocalIPAddressCompleted(
204       env_, j_voip_client_, webrtc::NativeToJavaString(env_, local_ip_address));
205 }
206 
SetEncoder(const std::string & encoder)207 void AndroidVoipClient::SetEncoder(const std::string& encoder) {
208   RTC_DCHECK_RUN_ON(voip_thread_.get());
209 
210   if (!channel_) {
211     RTC_LOG(LS_ERROR) << "Channel has not been created";
212     return;
213   }
214   for (const webrtc::AudioCodecSpec& codec : supported_codecs_) {
215     if (codec.format.name == encoder) {
216       webrtc::VoipResult result = voip_engine_->Codec().SetSendCodec(
217           *channel_, GetPayloadType(codec.format.name), codec.format);
218       RTC_CHECK(result == webrtc::VoipResult::kOk);
219       return;
220     }
221   }
222 }
223 
SetEncoder(JNIEnv * env,const webrtc::JavaParamRef<jstring> & j_encoder_string)224 void AndroidVoipClient::SetEncoder(
225     JNIEnv* env,
226     const webrtc::JavaParamRef<jstring>& j_encoder_string) {
227   const std::string& chosen_encoder =
228       webrtc::JavaToNativeString(env, j_encoder_string);
229   voip_thread_->PostTask(
230       [this, chosen_encoder] { SetEncoder(chosen_encoder); });
231 }
232 
SetDecoders(const std::vector<std::string> & decoders)233 void AndroidVoipClient::SetDecoders(const std::vector<std::string>& decoders) {
234   RTC_DCHECK_RUN_ON(voip_thread_.get());
235 
236   if (!channel_) {
237     RTC_LOG(LS_ERROR) << "Channel has not been created";
238     return;
239   }
240   std::map<int, webrtc::SdpAudioFormat> decoder_specs;
241   for (const webrtc::AudioCodecSpec& codec : supported_codecs_) {
242     if (std::find(decoders.begin(), decoders.end(), codec.format.name) !=
243         decoders.end()) {
244       decoder_specs.insert({GetPayloadType(codec.format.name), codec.format});
245     }
246   }
247 
248   webrtc::VoipResult result =
249       voip_engine_->Codec().SetReceiveCodecs(*channel_, decoder_specs);
250   RTC_CHECK(result == webrtc::VoipResult::kOk);
251 }
252 
SetDecoders(JNIEnv * env,const webrtc::JavaParamRef<jobject> & j_decoder_strings)253 void AndroidVoipClient::SetDecoders(
254     JNIEnv* env,
255     const webrtc::JavaParamRef<jobject>& j_decoder_strings) {
256   const std::vector<std::string>& chosen_decoders =
257       webrtc::JavaListToNativeVector<std::string, jstring>(
258           env, j_decoder_strings, &webrtc::JavaToNativeString);
259   voip_thread_->PostTask(
260       [this, chosen_decoders] { SetDecoders(chosen_decoders); });
261 }
262 
SetLocalAddress(const std::string & ip_address,const int port_number)263 void AndroidVoipClient::SetLocalAddress(const std::string& ip_address,
264                                         const int port_number) {
265   RTC_DCHECK_RUN_ON(voip_thread_.get());
266 
267   rtp_local_address_ = rtc::SocketAddress(ip_address, port_number);
268   rtcp_local_address_ = rtc::SocketAddress(ip_address, port_number + 1);
269 }
270 
SetLocalAddress(JNIEnv * env,const webrtc::JavaParamRef<jstring> & j_ip_address_string,jint j_port_number_int)271 void AndroidVoipClient::SetLocalAddress(
272     JNIEnv* env,
273     const webrtc::JavaParamRef<jstring>& j_ip_address_string,
274     jint j_port_number_int) {
275   const std::string& ip_address =
276       webrtc::JavaToNativeString(env, j_ip_address_string);
277   voip_thread_->PostTask([this, ip_address, j_port_number_int] {
278     SetLocalAddress(ip_address, j_port_number_int);
279   });
280 }
281 
SetRemoteAddress(const std::string & ip_address,const int port_number)282 void AndroidVoipClient::SetRemoteAddress(const std::string& ip_address,
283                                          const int port_number) {
284   RTC_DCHECK_RUN_ON(voip_thread_.get());
285 
286   rtp_remote_address_ = rtc::SocketAddress(ip_address, port_number);
287   rtcp_remote_address_ = rtc::SocketAddress(ip_address, port_number + 1);
288 }
289 
SetRemoteAddress(JNIEnv * env,const webrtc::JavaParamRef<jstring> & j_ip_address_string,jint j_port_number_int)290 void AndroidVoipClient::SetRemoteAddress(
291     JNIEnv* env,
292     const webrtc::JavaParamRef<jstring>& j_ip_address_string,
293     jint j_port_number_int) {
294   const std::string& ip_address =
295       webrtc::JavaToNativeString(env, j_ip_address_string);
296   voip_thread_->PostTask([this, ip_address, j_port_number_int] {
297     SetRemoteAddress(ip_address, j_port_number_int);
298   });
299 }
300 
StartSession(JNIEnv * env)301 void AndroidVoipClient::StartSession(JNIEnv* env) {
302   RUN_ON_VOIP_THREAD(StartSession, env);
303 
304   // CreateChannel guarantees to return valid channel id.
305   channel_ = voip_engine_->Base().CreateChannel(this, absl::nullopt);
306 
307   rtp_socket_.reset(rtc::AsyncUDPSocket::Create(voip_thread_->socketserver(),
308                                                 rtp_local_address_));
309   if (!rtp_socket_) {
310     RTC_LOG_ERR(LS_ERROR) << "Socket creation failed";
311     Java_VoipClient_onStartSessionCompleted(env_, j_voip_client_,
312                                             /*isSuccessful=*/false);
313     return;
314   }
315   rtp_socket_->SignalReadPacket.connect(
316       this, &AndroidVoipClient::OnSignalReadRTPPacket);
317 
318   rtcp_socket_.reset(rtc::AsyncUDPSocket::Create(voip_thread_->socketserver(),
319                                                  rtcp_local_address_));
320   if (!rtcp_socket_) {
321     RTC_LOG_ERR(LS_ERROR) << "Socket creation failed";
322     Java_VoipClient_onStartSessionCompleted(env_, j_voip_client_,
323                                             /*isSuccessful=*/false);
324     return;
325   }
326   rtcp_socket_->SignalReadPacket.connect(
327       this, &AndroidVoipClient::OnSignalReadRTCPPacket);
328   Java_VoipClient_onStartSessionCompleted(env_, j_voip_client_,
329                                           /*isSuccessful=*/true);
330 }
331 
StopSession(JNIEnv * env)332 void AndroidVoipClient::StopSession(JNIEnv* env) {
333   RUN_ON_VOIP_THREAD(StopSession, env);
334 
335   if (!channel_) {
336     RTC_LOG(LS_ERROR) << "Channel has not been created";
337     Java_VoipClient_onStopSessionCompleted(env_, j_voip_client_,
338                                            /*isSuccessful=*/false);
339     return;
340   }
341   if (voip_engine_->Base().StopSend(*channel_) != webrtc::VoipResult::kOk ||
342       voip_engine_->Base().StopPlayout(*channel_) != webrtc::VoipResult::kOk) {
343     Java_VoipClient_onStopSessionCompleted(env_, j_voip_client_,
344                                            /*isSuccessful=*/false);
345     return;
346   }
347 
348   rtp_socket_->Close();
349   rtcp_socket_->Close();
350 
351   webrtc::VoipResult result = voip_engine_->Base().ReleaseChannel(*channel_);
352   RTC_CHECK(result == webrtc::VoipResult::kOk);
353 
354   channel_ = absl::nullopt;
355   Java_VoipClient_onStopSessionCompleted(env_, j_voip_client_,
356                                          /*isSuccessful=*/true);
357 }
358 
StartSend(JNIEnv * env)359 void AndroidVoipClient::StartSend(JNIEnv* env) {
360   RUN_ON_VOIP_THREAD(StartSend, env);
361 
362   if (!channel_) {
363     RTC_LOG(LS_ERROR) << "Channel has not been created";
364     Java_VoipClient_onStartSendCompleted(env_, j_voip_client_,
365                                          /*isSuccessful=*/false);
366     return;
367   }
368   bool sending_started =
369       (voip_engine_->Base().StartSend(*channel_) == webrtc::VoipResult::kOk);
370   Java_VoipClient_onStartSendCompleted(env_, j_voip_client_, sending_started);
371 }
372 
StopSend(JNIEnv * env)373 void AndroidVoipClient::StopSend(JNIEnv* env) {
374   RUN_ON_VOIP_THREAD(StopSend, env);
375 
376   if (!channel_) {
377     RTC_LOG(LS_ERROR) << "Channel has not been created";
378     Java_VoipClient_onStopSendCompleted(env_, j_voip_client_,
379                                         /*isSuccessful=*/false);
380     return;
381   }
382   bool sending_stopped =
383       (voip_engine_->Base().StopSend(*channel_) == webrtc::VoipResult::kOk);
384   Java_VoipClient_onStopSendCompleted(env_, j_voip_client_, sending_stopped);
385 }
386 
StartPlayout(JNIEnv * env)387 void AndroidVoipClient::StartPlayout(JNIEnv* env) {
388   RUN_ON_VOIP_THREAD(StartPlayout, env);
389 
390   if (!channel_) {
391     RTC_LOG(LS_ERROR) << "Channel has not been created";
392     Java_VoipClient_onStartPlayoutCompleted(env_, j_voip_client_,
393                                             /*isSuccessful=*/false);
394     return;
395   }
396   bool playout_started =
397       (voip_engine_->Base().StartPlayout(*channel_) == webrtc::VoipResult::kOk);
398   Java_VoipClient_onStartPlayoutCompleted(env_, j_voip_client_,
399                                           playout_started);
400 }
401 
StopPlayout(JNIEnv * env)402 void AndroidVoipClient::StopPlayout(JNIEnv* env) {
403   RUN_ON_VOIP_THREAD(StopPlayout, env);
404 
405   if (!channel_) {
406     RTC_LOG(LS_ERROR) << "Channel has not been created";
407     Java_VoipClient_onStopPlayoutCompleted(env_, j_voip_client_,
408                                            /*isSuccessful=*/false);
409     return;
410   }
411   bool playout_stopped =
412       (voip_engine_->Base().StopPlayout(*channel_) == webrtc::VoipResult::kOk);
413   Java_VoipClient_onStopPlayoutCompleted(env_, j_voip_client_, playout_stopped);
414 }
415 
Delete(JNIEnv * env)416 void AndroidVoipClient::Delete(JNIEnv* env) {
417   delete this;
418 }
419 
SendRtpPacket(const std::vector<uint8_t> & packet_copy)420 void AndroidVoipClient::SendRtpPacket(const std::vector<uint8_t>& packet_copy) {
421   RTC_DCHECK_RUN_ON(voip_thread_.get());
422 
423   if (!rtp_socket_->SendTo(packet_copy.data(), packet_copy.size(),
424                            rtp_remote_address_, rtc::PacketOptions())) {
425     RTC_LOG(LS_ERROR) << "Failed to send RTP packet";
426   }
427 }
428 
SendRtp(const uint8_t * packet,size_t length,const webrtc::PacketOptions & options)429 bool AndroidVoipClient::SendRtp(const uint8_t* packet,
430                                 size_t length,
431                                 const webrtc::PacketOptions& options) {
432   std::vector<uint8_t> packet_copy(packet, packet + length);
433   voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
434     SendRtpPacket(packet_copy);
435   });
436   return true;
437 }
438 
SendRtcpPacket(const std::vector<uint8_t> & packet_copy)439 void AndroidVoipClient::SendRtcpPacket(
440     const std::vector<uint8_t>& packet_copy) {
441   RTC_DCHECK_RUN_ON(voip_thread_.get());
442 
443   if (!rtcp_socket_->SendTo(packet_copy.data(), packet_copy.size(),
444                             rtcp_remote_address_, rtc::PacketOptions())) {
445     RTC_LOG(LS_ERROR) << "Failed to send RTCP packet";
446   }
447 }
448 
SendRtcp(const uint8_t * packet,size_t length)449 bool AndroidVoipClient::SendRtcp(const uint8_t* packet, size_t length) {
450   std::vector<uint8_t> packet_copy(packet, packet + length);
451   voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
452     SendRtcpPacket(packet_copy);
453   });
454   return true;
455 }
456 
ReadRTPPacket(const std::vector<uint8_t> & packet_copy)457 void AndroidVoipClient::ReadRTPPacket(const std::vector<uint8_t>& packet_copy) {
458   RTC_DCHECK_RUN_ON(voip_thread_.get());
459 
460   if (!channel_) {
461     RTC_LOG(LS_ERROR) << "Channel has not been created";
462     return;
463   }
464   webrtc::VoipResult result = voip_engine_->Network().ReceivedRTPPacket(
465       *channel_,
466       rtc::ArrayView<const uint8_t>(packet_copy.data(), packet_copy.size()));
467   RTC_CHECK(result == webrtc::VoipResult::kOk);
468 }
469 
OnSignalReadRTPPacket(rtc::AsyncPacketSocket * socket,const char * rtp_packet,size_t size,const rtc::SocketAddress & addr,const int64_t & timestamp)470 void AndroidVoipClient::OnSignalReadRTPPacket(rtc::AsyncPacketSocket* socket,
471                                               const char* rtp_packet,
472                                               size_t size,
473                                               const rtc::SocketAddress& addr,
474                                               const int64_t& timestamp) {
475   std::vector<uint8_t> packet_copy(rtp_packet, rtp_packet + size);
476   voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
477     ReadRTPPacket(packet_copy);
478   });
479 }
480 
ReadRTCPPacket(const std::vector<uint8_t> & packet_copy)481 void AndroidVoipClient::ReadRTCPPacket(
482     const std::vector<uint8_t>& packet_copy) {
483   RTC_DCHECK_RUN_ON(voip_thread_.get());
484 
485   if (!channel_) {
486     RTC_LOG(LS_ERROR) << "Channel has not been created";
487     return;
488   }
489   webrtc::VoipResult result = voip_engine_->Network().ReceivedRTCPPacket(
490       *channel_,
491       rtc::ArrayView<const uint8_t>(packet_copy.data(), packet_copy.size()));
492   RTC_CHECK(result == webrtc::VoipResult::kOk);
493 }
494 
OnSignalReadRTCPPacket(rtc::AsyncPacketSocket * socket,const char * rtcp_packet,size_t size,const rtc::SocketAddress & addr,const int64_t & timestamp)495 void AndroidVoipClient::OnSignalReadRTCPPacket(rtc::AsyncPacketSocket* socket,
496                                                const char* rtcp_packet,
497                                                size_t size,
498                                                const rtc::SocketAddress& addr,
499                                                const int64_t& timestamp) {
500   std::vector<uint8_t> packet_copy(rtcp_packet, rtcp_packet + size);
501   voip_thread_->PostTask([this, packet_copy = std::move(packet_copy)] {
502     ReadRTCPPacket(packet_copy);
503   });
504 }
505 
JNI_VoipClient_CreateClient(JNIEnv * env,const webrtc::JavaParamRef<jobject> & application_context,const webrtc::JavaParamRef<jobject> & j_voip_client)506 static jlong JNI_VoipClient_CreateClient(
507     JNIEnv* env,
508     const webrtc::JavaParamRef<jobject>& application_context,
509     const webrtc::JavaParamRef<jobject>& j_voip_client) {
510   return webrtc::NativeToJavaPointer(
511       AndroidVoipClient::Create(env, application_context, j_voip_client));
512 }
513 
514 }  // namespace webrtc_examples
515