|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "CSFLog.h" |
|
6 #include "timecard.h" |
|
7 |
|
8 #include "CC_Common.h" |
|
9 |
|
10 #include "CC_SIPCCCall.h" |
|
11 #include "CC_SIPCCCallInfo.h" |
|
12 #include "VcmSIPCCBinding.h" |
|
13 #include "CSFVideoTermination.h" |
|
14 #include "CSFAudioTermination.h" |
|
15 #include "CSFAudioControl.h" |
|
16 |
|
17 extern "C" |
|
18 { |
|
19 #include "ccapi_call.h" |
|
20 #include "ccapi_call_listener.h" |
|
21 #include "config_api.h" |
|
22 } |
|
23 |
|
24 using namespace std; |
|
25 using namespace CSF; |
|
26 |
|
27 static const char* logTag = "CC_SIPCCCall"; |
|
28 |
|
29 CSF_IMPLEMENT_WRAP(CC_SIPCCCall, cc_call_handle_t); |
|
30 |
|
31 CC_SIPCCCall::CC_SIPCCCall (cc_call_handle_t aCallHandle) : |
|
32 callHandle(aCallHandle), |
|
33 pMediaData(new CC_SIPCCCallMediaData(nullptr, false, false, -1)), |
|
34 m_lock("CC_SIPCCCall") |
|
35 { |
|
36 CSFLogInfo( logTag, "Creating CC_SIPCCCall %u", callHandle ); |
|
37 |
|
38 AudioControl * audioControl = VcmSIPCCBinding::getAudioControl(); |
|
39 |
|
40 if(audioControl) |
|
41 { |
|
42 pMediaData->volume = audioControl->getDefaultVolume(); |
|
43 } |
|
44 } |
|
45 |
|
46 |
|
47 /* |
|
48 CCAPI_CALL_EV_CAPABILITY -- From phone team: "...CCAPI_CALL_EV_CAPABILITY is generated for the capability changes but we decided to |
|
49 suppress it if it's due to state changes. We found it redundant as a state change implicitly implies a |
|
50 capability change. This event will still be generated if the capability changes without a state change. |
|
51 CCAPI_CALL_EV_CALLINFO -- From phone team: "...CCAPI_CALL_EV_CALLINFO is generated for any caller id related changes including |
|
52 called/calling/redirecting name/number etc..." |
|
53 CCAPI_CALL_EV_PLACED_CALLINFO -- From phone team: "CCAPI_CALL_EV_PLACED_CALLINFO was a trigger to update the placed call history and |
|
54 gives the actual number dialed out. I think this event can be deprecated." |
|
55 */ |
|
56 |
|
57 /* |
|
58 CallState |
|
59 |
|
60 REORDER: You get this if you misdial a number. |
|
61 */ |
|
62 |
|
63 // This function sets the remote window parameters for the call. Note that it currently only tolerates a single |
|
64 // video stream on the call and would need to be updated to handle multiple remote video streams for conferencing. |
|
65 void CC_SIPCCCall::setRemoteWindow (VideoWindowHandle window) |
|
66 { |
|
67 VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); |
|
68 pMediaData->remoteWindow = window; |
|
69 |
|
70 if (!pVideo) |
|
71 { |
|
72 CSFLogWarn( logTag, "setRemoteWindow: no video provider found"); |
|
73 return; |
|
74 } |
|
75 |
|
76 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
77 { |
|
78 if (entry->second.isVideo) |
|
79 { |
|
80 // first video stream found |
|
81 int streamId = entry->first; |
|
82 pVideo->setRemoteWindow(streamId, pMediaData->remoteWindow); |
|
83 |
|
84 return; |
|
85 } |
|
86 } |
|
87 CSFLogInfo( logTag, "setRemoteWindow:no video stream found in call %u", callHandle ); |
|
88 } |
|
89 |
|
90 int CC_SIPCCCall::setExternalRenderer(VideoFormat vFormat, ExternalRendererHandle renderer) |
|
91 { |
|
92 VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); |
|
93 pMediaData->extRenderer = renderer; |
|
94 pMediaData->videoFormat = vFormat; |
|
95 |
|
96 if (!pVideo) |
|
97 { |
|
98 CSFLogWarn( logTag, "setExternalRenderer: no video provider found"); |
|
99 return -1; |
|
100 } |
|
101 |
|
102 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
103 { |
|
104 if (entry->second.isVideo) |
|
105 { |
|
106 // first video stream found |
|
107 int streamId = entry->first; |
|
108 return pVideo->setExternalRenderer(streamId, pMediaData->videoFormat, pMediaData->extRenderer); |
|
109 } |
|
110 } |
|
111 CSFLogInfo( logTag, "setExternalRenderer:no video stream found in call %u", callHandle ); |
|
112 return -1; |
|
113 } |
|
114 |
|
115 void CC_SIPCCCall::sendIFrame() |
|
116 { |
|
117 VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); |
|
118 |
|
119 if (pVideo) |
|
120 { |
|
121 pVideo->sendIFrame(callHandle); |
|
122 } |
|
123 } |
|
124 |
|
125 CC_CallInfoPtr CC_SIPCCCall::getCallInfo () |
|
126 { |
|
127 cc_callinfo_ref_t callInfo = CCAPI_Call_getCallInfo(callHandle); |
|
128 CC_SIPCCCallInfoPtr callInfoPtr = CC_SIPCCCallInfo::wrap(callInfo); |
|
129 callInfoPtr->setMediaData( pMediaData); |
|
130 return callInfoPtr.get(); |
|
131 } |
|
132 |
|
133 |
|
134 // |
|
135 // Operations - The following function are actions that can be taken the execute an operation on the Call, ie calls |
|
136 // down to pSIPCC to originate a call, end a call etc. |
|
137 // |
|
138 |
|
139 bool CC_SIPCCCall::originateCall (cc_sdp_direction_t video_pref, const string & digits) |
|
140 { |
|
141 return (CCAPI_Call_originateCall(callHandle, video_pref, digits.c_str()) == CC_SUCCESS); |
|
142 } |
|
143 |
|
144 bool CC_SIPCCCall::answerCall (cc_sdp_direction_t video_pref) |
|
145 { |
|
146 return (CCAPI_Call_answerCall(callHandle, video_pref) == CC_SUCCESS); |
|
147 } |
|
148 |
|
149 bool CC_SIPCCCall::hold (cc_hold_reason_t reason) |
|
150 { |
|
151 return (CCAPI_Call_hold(callHandle, reason) == CC_SUCCESS); |
|
152 } |
|
153 |
|
154 bool CC_SIPCCCall::resume (cc_sdp_direction_t video_pref) |
|
155 { |
|
156 return (CCAPI_Call_resume(callHandle, video_pref) == CC_SUCCESS); |
|
157 } |
|
158 |
|
159 bool CC_SIPCCCall::endCall() |
|
160 { |
|
161 return (CCAPI_Call_endCall(callHandle) == CC_SUCCESS); |
|
162 } |
|
163 |
|
164 bool CC_SIPCCCall::sendDigit (cc_digit_t digit) |
|
165 { |
|
166 AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); |
|
167 mozilla::MutexAutoLock lock(m_lock); |
|
168 |
|
169 // Convert public digit (as enum or char) to RFC2833 form. |
|
170 int digitId = -1; |
|
171 switch(digit) |
|
172 { |
|
173 case '0': |
|
174 digitId = 0; |
|
175 break; |
|
176 case '1': |
|
177 digitId = 1; |
|
178 break; |
|
179 case '2': |
|
180 digitId = 2; |
|
181 break; |
|
182 case '3': |
|
183 digitId = 3; |
|
184 break; |
|
185 case '4': |
|
186 digitId = 4; |
|
187 break; |
|
188 case '5': |
|
189 digitId = 5; |
|
190 break; |
|
191 case '6': |
|
192 digitId = 6; |
|
193 break; |
|
194 case '7': |
|
195 digitId = 7; |
|
196 break; |
|
197 case '8': |
|
198 digitId = 8; |
|
199 break; |
|
200 case '9': |
|
201 digitId = 9; |
|
202 break; |
|
203 case '*': |
|
204 digitId = 10; |
|
205 break; |
|
206 case '#': |
|
207 digitId = 11; |
|
208 break; |
|
209 case 'A': |
|
210 digitId = 12; |
|
211 break; |
|
212 case 'B': |
|
213 digitId = 13; |
|
214 break; |
|
215 case 'C': |
|
216 digitId = 14; |
|
217 break; |
|
218 case 'D': |
|
219 digitId = 15; |
|
220 break; |
|
221 case '+': |
|
222 digitId = 16; |
|
223 break; |
|
224 } |
|
225 |
|
226 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
227 { |
|
228 if (entry->second.isVideo == false) |
|
229 { |
|
230 // first is the streamId |
|
231 if (pAudio->sendDtmf(entry->first, digitId) != 0) |
|
232 { |
|
233 // We have sent a digit, done. |
|
234 break; |
|
235 } |
|
236 else |
|
237 { |
|
238 CSFLogWarn( logTag, "sendDigit:sendDtmf returned fail"); |
|
239 } |
|
240 } |
|
241 } |
|
242 return (CCAPI_Call_sendDigit(callHandle, digit) == CC_SUCCESS); |
|
243 } |
|
244 |
|
245 bool CC_SIPCCCall::backspace() |
|
246 { |
|
247 return (CCAPI_Call_backspace(callHandle) == CC_SUCCESS); |
|
248 } |
|
249 |
|
250 bool CC_SIPCCCall::redial (cc_sdp_direction_t video_pref) |
|
251 { |
|
252 return (CCAPI_Call_redial(callHandle, video_pref) == CC_SUCCESS); |
|
253 } |
|
254 |
|
255 bool CC_SIPCCCall::initiateCallForwardAll() |
|
256 { |
|
257 return (CCAPI_Call_initiateCallForwardAll(callHandle) == CC_SUCCESS); |
|
258 } |
|
259 |
|
260 bool CC_SIPCCCall::endConsultativeCall() |
|
261 { |
|
262 return (CCAPI_Call_endConsultativeCall(callHandle) == CC_SUCCESS); |
|
263 } |
|
264 |
|
265 bool CC_SIPCCCall::conferenceStart (cc_sdp_direction_t video_pref) |
|
266 { |
|
267 return (CCAPI_Call_conferenceStart(callHandle, video_pref) == CC_SUCCESS); |
|
268 } |
|
269 |
|
270 bool CC_SIPCCCall::conferenceComplete (CC_CallPtr otherLeg, cc_sdp_direction_t video_pref) |
|
271 { |
|
272 return (CCAPI_Call_conferenceComplete(callHandle, ((CC_SIPCCCall*)otherLeg.get())->callHandle, video_pref) == CC_SUCCESS); |
|
273 } |
|
274 |
|
275 bool CC_SIPCCCall::transferStart (cc_sdp_direction_t video_pref) |
|
276 { |
|
277 return (CCAPI_Call_transferStart(callHandle, video_pref) == CC_SUCCESS); |
|
278 } |
|
279 |
|
280 bool CC_SIPCCCall::transferComplete (CC_CallPtr otherLeg, |
|
281 cc_sdp_direction_t video_pref) |
|
282 { |
|
283 return (CCAPI_Call_transferComplete(callHandle, ((CC_SIPCCCall*)otherLeg.get())->callHandle, video_pref) == CC_SUCCESS); |
|
284 } |
|
285 |
|
286 bool CC_SIPCCCall::cancelTransferOrConferenceFeature() |
|
287 { |
|
288 return (CCAPI_Call_cancelTransferOrConferenceFeature(callHandle) == CC_SUCCESS); |
|
289 } |
|
290 |
|
291 bool CC_SIPCCCall::directTransfer (CC_CallPtr target) |
|
292 { |
|
293 return (CCAPI_Call_directTransfer(callHandle, ((CC_SIPCCCall*)target.get())->callHandle) == CC_SUCCESS); |
|
294 } |
|
295 |
|
296 bool CC_SIPCCCall::joinAcrossLine (CC_CallPtr target) |
|
297 { |
|
298 return (CCAPI_Call_joinAcrossLine(callHandle, ((CC_SIPCCCall*)target.get())->callHandle) == CC_SUCCESS); |
|
299 } |
|
300 |
|
301 bool CC_SIPCCCall::blfCallPickup (cc_sdp_direction_t video_pref, const string & speed) |
|
302 { |
|
303 return (CCAPI_Call_blfCallPickup(callHandle, video_pref, speed.c_str()) == CC_SUCCESS); |
|
304 } |
|
305 |
|
306 bool CC_SIPCCCall::select() |
|
307 { |
|
308 return (CCAPI_Call_select(callHandle) == CC_SUCCESS); |
|
309 } |
|
310 |
|
311 bool CC_SIPCCCall::updateVideoMediaCap (cc_sdp_direction_t video_pref) |
|
312 { |
|
313 return (CCAPI_Call_updateVideoMediaCap(callHandle, video_pref) == CC_SUCCESS); |
|
314 } |
|
315 |
|
316 bool CC_SIPCCCall::sendInfo (const string & infopackage, const string & infotype, const string & infobody) |
|
317 { |
|
318 return (CCAPI_Call_sendInfo(callHandle, infopackage.c_str(), infotype.c_str(), infobody.c_str()) == CC_SUCCESS); |
|
319 } |
|
320 |
|
321 bool CC_SIPCCCall::muteAudio(void) |
|
322 { |
|
323 return setAudioMute(true); |
|
324 } |
|
325 |
|
326 bool CC_SIPCCCall::unmuteAudio() |
|
327 { |
|
328 return setAudioMute(false); |
|
329 } |
|
330 |
|
331 bool CC_SIPCCCall::muteVideo() |
|
332 { |
|
333 return setVideoMute(true); |
|
334 } |
|
335 |
|
336 bool CC_SIPCCCall::unmuteVideo() |
|
337 { |
|
338 return setVideoMute(false); |
|
339 } |
|
340 |
|
341 bool CC_SIPCCCall::setAudioMute(bool mute) |
|
342 { |
|
343 bool returnCode = false; |
|
344 AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); |
|
345 pMediaData->audioMuteState = mute; |
|
346 // we need to set the mute status of all audio streams in the map |
|
347 { |
|
348 mozilla::MutexAutoLock lock(m_lock); |
|
349 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
350 { |
|
351 if (entry->second.isVideo == false) |
|
352 { |
|
353 // first is the streamId |
|
354 if (pAudio->mute(entry->first, mute)) |
|
355 { |
|
356 // We have muted at least one stream |
|
357 returnCode = true; |
|
358 } |
|
359 else |
|
360 { |
|
361 CSFLogWarn( logTag, "setAudioMute:audio mute returned fail"); |
|
362 } |
|
363 } |
|
364 } |
|
365 } |
|
366 |
|
367 if (CCAPI_Call_setAudioMute(callHandle, mute) != CC_SUCCESS) |
|
368 { |
|
369 returnCode = false; |
|
370 } |
|
371 |
|
372 return returnCode; |
|
373 } |
|
374 |
|
375 bool CC_SIPCCCall::setVideoMute(bool mute) |
|
376 { |
|
377 bool returnCode = false; |
|
378 VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); |
|
379 pMediaData->videoMuteState = mute; |
|
380 // we need to set the mute status of all audio streams in the map |
|
381 { |
|
382 mozilla::MutexAutoLock lock(m_lock); |
|
383 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
384 { |
|
385 if (entry->second.isVideo == true) |
|
386 { |
|
387 // first is the streamId |
|
388 if (pVideo->mute(entry->first, mute)) |
|
389 { |
|
390 // We have muted at least one stream |
|
391 returnCode = true; |
|
392 } |
|
393 else |
|
394 { |
|
395 CSFLogWarn( logTag, "setVideoMute:video mute returned fail"); |
|
396 } |
|
397 } |
|
398 } |
|
399 } |
|
400 |
|
401 if (CCAPI_Call_setVideoMute(callHandle, mute) != CC_SUCCESS) |
|
402 { |
|
403 returnCode = false; |
|
404 } |
|
405 |
|
406 return returnCode; |
|
407 } |
|
408 |
|
409 void CC_SIPCCCall::addStream(int streamId, bool isVideo) |
|
410 { |
|
411 |
|
412 CSFLogInfo( logTag, "addStream: %d video=%s callhandle=%u", |
|
413 streamId, isVideo ? "TRUE" : "FALSE", callHandle); |
|
414 { |
|
415 mozilla::MutexAutoLock lock(m_lock); |
|
416 pMediaData->streamMap[streamId].isVideo = isVideo; |
|
417 } |
|
418 // The new stream needs to be given any properties that the call has for it. |
|
419 // At the moment the only candidate is the muted state |
|
420 if (isVideo) |
|
421 { |
|
422 #ifndef NO_WEBRTC_VIDEO |
|
423 VideoTermination * pVideo = VcmSIPCCBinding::getVideoTermination(); |
|
424 |
|
425 // if there is a window for this call apply it to the stream |
|
426 if ( pMediaData->remoteWindow != nullptr) |
|
427 { |
|
428 pVideo->setRemoteWindow(streamId, pMediaData->remoteWindow); |
|
429 } |
|
430 else |
|
431 { |
|
432 CSFLogInfo( logTag, "addStream: remoteWindow is NULL"); |
|
433 } |
|
434 |
|
435 if(pMediaData->extRenderer != nullptr) |
|
436 { |
|
437 pVideo->setExternalRenderer(streamId, pMediaData->videoFormat, pMediaData->extRenderer); |
|
438 } |
|
439 else |
|
440 { |
|
441 CSFLogInfo( logTag, "addStream: externalRenderer is NULL"); |
|
442 |
|
443 } |
|
444 |
|
445 |
|
446 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
447 { |
|
448 if (entry->second.isVideo == false) |
|
449 { |
|
450 // first is the streamId |
|
451 pVideo->setAudioStreamId(entry->first); |
|
452 } |
|
453 } |
|
454 if (!pVideo->mute(streamId, pMediaData->videoMuteState)) |
|
455 { |
|
456 CSFLogError( logTag, "setting video mute state failed for new stream: %d", streamId); |
|
457 } else |
|
458 { |
|
459 CSFLogError( logTag, "setting video mute state SUCCEEDED for new stream: %d", streamId); |
|
460 |
|
461 } |
|
462 #endif |
|
463 } |
|
464 else |
|
465 { |
|
466 AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); |
|
467 if (!pAudio->mute(streamId, pMediaData->audioMuteState)) |
|
468 { |
|
469 CSFLogError( logTag, "setting audio mute state failed for new stream: %d", streamId); |
|
470 } |
|
471 if (!pAudio->setVolume(streamId, pMediaData->volume)) |
|
472 { |
|
473 CSFLogError( logTag, "setting volume state failed for new stream: %d", streamId); |
|
474 } |
|
475 } |
|
476 } |
|
477 |
|
478 void CC_SIPCCCall::removeStream(int streamId) |
|
479 { |
|
480 mozilla::MutexAutoLock lock(m_lock); |
|
481 |
|
482 if ( pMediaData->streamMap.erase(streamId) != 1) |
|
483 { |
|
484 CSFLogError( logTag, "removeStream stream that was never in the streamMap: %d", streamId); |
|
485 } |
|
486 } |
|
487 |
|
488 bool CC_SIPCCCall::setVolume(int volume) |
|
489 { |
|
490 bool returnCode = false; |
|
491 |
|
492 AudioTermination * pAudio = VcmSIPCCBinding::getAudioTermination(); |
|
493 { |
|
494 mozilla::MutexAutoLock lock(m_lock); |
|
495 for (StreamMapType::iterator entry = pMediaData->streamMap.begin(); entry != pMediaData->streamMap.end(); entry++) |
|
496 { |
|
497 if (entry->second.isVideo == false) |
|
498 { |
|
499 // first is the streamId |
|
500 int streamId = entry->first; |
|
501 if (pAudio->setVolume(streamId, volume)) |
|
502 { |
|
503 //We have changed the volume on at least one stream, |
|
504 //so persist the new volume as the call volume, |
|
505 //and report success for the volume change, even if it |
|
506 //fails on other streams |
|
507 pMediaData->volume = volume; |
|
508 returnCode = true; |
|
509 } |
|
510 else |
|
511 { |
|
512 CSFLogWarn( logTag, "setVolume:set volume on stream %d returned fail", |
|
513 streamId); |
|
514 } |
|
515 } |
|
516 } |
|
517 } |
|
518 return returnCode; |
|
519 } |
|
520 |
|
521 CC_SIPCCCallMediaDataPtr CC_SIPCCCall::getMediaData() |
|
522 { |
|
523 return pMediaData; |
|
524 } |
|
525 |
|
526 void CC_SIPCCCall::originateP2PCall (cc_sdp_direction_t video_pref, const std::string & digits, const std::string & ip) |
|
527 { |
|
528 CCAPI_Config_set_server_address(ip.c_str()); |
|
529 CCAPI_Call_originateCall(callHandle, video_pref, digits.c_str()); |
|
530 } |
|
531 |
|
532 /* |
|
533 * This method works asynchronously, is an onCallEvent with the resulting SDP |
|
534 */ |
|
535 void CC_SIPCCCall::createOffer (cc_media_constraints_t *constraints, |
|
536 Timecard *tc) { |
|
537 CCAPI_CreateOffer(callHandle, constraints, tc); |
|
538 } |
|
539 /* |
|
540 * This method works asynchronously, there is onCallEvent with the resulting SDP |
|
541 */ |
|
542 void CC_SIPCCCall::createAnswer (cc_media_constraints_t *constraints, |
|
543 Timecard *tc) { |
|
544 CCAPI_CreateAnswer(callHandle, constraints, tc); |
|
545 |
|
546 } |
|
547 |
|
548 void CC_SIPCCCall::setLocalDescription(cc_jsep_action_t action, |
|
549 const std::string & sdp, |
|
550 Timecard *tc) { |
|
551 CCAPI_SetLocalDescription(callHandle, action, sdp.c_str(), tc); |
|
552 } |
|
553 |
|
554 void CC_SIPCCCall::setRemoteDescription(cc_jsep_action_t action, |
|
555 const std::string & sdp, |
|
556 Timecard *tc) { |
|
557 CCAPI_SetRemoteDescription(callHandle, action, sdp.c_str(), tc); |
|
558 } |
|
559 |
|
560 void CC_SIPCCCall::setPeerConnection(const std::string& handle) |
|
561 { |
|
562 CSFLogDebug(logTag, "setPeerConnection"); |
|
563 |
|
564 peerconnection = handle; // Cache this here. we need it to make the CC_SIPCCCallInfo |
|
565 CCAPI_SetPeerConnection(callHandle, handle.c_str()); |
|
566 } |
|
567 |
|
568 const std::string& CC_SIPCCCall::getPeerConnection() const { |
|
569 return peerconnection; |
|
570 } |
|
571 |
|
572 void CC_SIPCCCall::addStream(cc_media_stream_id_t stream_id, |
|
573 cc_media_track_id_t track_id, |
|
574 cc_media_type_t media_type, |
|
575 cc_media_constraints_t *constraints) { |
|
576 CCAPI_AddStream(callHandle, stream_id, track_id, media_type, constraints); |
|
577 } |
|
578 |
|
579 void CC_SIPCCCall::removeStream(cc_media_stream_id_t stream_id, cc_media_track_id_t track_id, cc_media_type_t media_type) { |
|
580 CCAPI_RemoveStream(callHandle, stream_id, track_id, media_type); |
|
581 } |
|
582 |
|
583 void CC_SIPCCCall::addICECandidate(const std::string & candidate, |
|
584 const std::string & mid, |
|
585 unsigned short level, |
|
586 Timecard *tc) { |
|
587 CCAPI_AddICECandidate(callHandle, candidate.c_str(), mid.c_str(), |
|
588 (cc_level_t) level, tc); |
|
589 } |