Commit 6dc6e0d4 authored by Ghislain MARY's avatar Ghislain MARY
Browse files

Fix DTMF handling.

parent 4d277538
......@@ -366,12 +366,16 @@ static void vfu_request(SalOp *op) {
L_GET_PRIVATE(mediaSession)->sendVfu();
}
static void dtmf_received(SalOp *op, char dtmf){
#if 0
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
if (!call) return;
linphone_call_notify_dtmf_received(call, dtmf);
#endif
static void dtmf_received(SalOp *op, char dtmf) {
LinphonePrivate::CallSession *session = reinterpret_cast<LinphonePrivate::CallSession *>(op->get_user_pointer());
if (!session)
return;
LinphonePrivate::MediaSession *mediaSession = dynamic_cast<LinphonePrivate::MediaSession *>(session);
if (!mediaSession) {
ms_warning("DTMF received but no MediaSession!");
return;
}
L_GET_PRIVATE(mediaSession)->dtmfReceived(dtmf);
}
static void call_refer_received(SalOp *op, const SalAddress *referto){
......
......@@ -51,37 +51,18 @@ L_DECLARE_C_OBJECT_IMPL_WITH_XTORS(Call,
/* TODO: all the fields need to be removed */
struct _LinphoneCore *core;
LinphoneErrorInfo *ei;
SalMediaDescription *localdesc;
SalMediaDescription *resultdesc;
struct _LinphoneCallLog *log;
LinphonePrivate::SalOp *op;
LinphonePrivate::SalOp *ping_op;
LinphoneCallState transfer_state; /*idle if no transfer*/
struct _AudioStream *audiostream; /**/
struct _VideoStream *videostream;
struct _TextStream *textstream;
MSAudioEndpoint *endpoint; /*used for conferencing*/
char *refer_to;
LinphoneCallParams *params;
LinphoneCallParams *current_params;
LinphoneCallParams *remote_params;
LinphoneCallStats *audio_stats;
LinphoneCallStats *video_stats;
LinphoneCallStats *text_stats;
LinphoneCall *referer; /*when this call is the result of a transfer, referer is set to the original call that caused the transfer*/
LinphoneCall *transfer_target;/*if this call received a transfer request, then transfer_target points to the new call created to the refer target */
LinphonePlayer *player;
char *dtmf_sequence; /*DTMF sequence needed to be sent using #dtmfs_timer*/
belle_sip_source_t *dtmfs_timer; /*DTMF timer needed to send a DTMF sequence*/
LinphoneChatRoom *chat_room;
LinphoneConference *conf_ref; /**> Point on the associated conference if this call is part of a conference. NULL instead. */
bool_t refer_pending;
bool_t defer_update;
bool_t was_automatically_paused;
bool_t paused_by_app;
bool_t broken; /*set to TRUE when the call is in broken state due to network disconnection or transport */
bool_t need_localip_refresh;
bool_t reinvite_on_cancel_response_requested;
bool_t non_op_error; /*set when the LinphoneErrorInfo was set at higher level than sal*/
)
......@@ -106,34 +87,10 @@ static void _linphone_call_destructor (LinphoneCall *call) {
call->remoteAddressCache = nullptr;
}
bctbx_list_free_with_data(call->callbacks, (bctbx_list_free_func)linphone_call_cbs_unref);
if (call->audio_stats) {
linphone_call_stats_unref(call->audio_stats);
call->audio_stats = nullptr;
}
if (call->video_stats) {
linphone_call_stats_unref(call->video_stats);
call->video_stats = nullptr;
}
if (call->text_stats) {
linphone_call_stats_unref(call->text_stats);
call->text_stats = nullptr;
}
if (call->op) {
call->op->release();
call->op=nullptr;
}
if (call->resultdesc) {
sal_media_description_unref(call->resultdesc);
call->resultdesc=nullptr;
}
if (call->localdesc) {
sal_media_description_unref(call->localdesc);
call->localdesc=nullptr;
}
if (call->ping_op) {
call->ping_op->release();
call->ping_op=nullptr;
}
if (call->refer_to){
ms_free(call->refer_to);
call->refer_to=nullptr;
......@@ -146,25 +103,6 @@ static void _linphone_call_destructor (LinphoneCall *call) {
linphone_call_unref(call->transfer_target);
call->transfer_target=nullptr;
}
if (call->log) {
linphone_call_log_unref(call->log);
call->log=nullptr;
}
if (call->dtmfs_timer) {
linphone_call_cancel_dtmfs(call);
}
if (call->params){
linphone_call_params_unref(call->params);
call->params=nullptr;
}
if (call->current_params){
linphone_call_params_unref(call->current_params);
call->current_params=nullptr;
}
if (call->remote_params) {
linphone_call_params_unref(call->remote_params);
call->remote_params=nullptr;
}
if (call->ei) linphone_error_info_unref(call->ei);
}
......@@ -191,18 +129,6 @@ void linphone_call_set_state (LinphoneCall *call, LinphoneCallState cstate, cons
void linphone_call_init_media_streams (LinphoneCall *call) {}
#if 0
static int dtmf_tab[16]={'0','1','2','3','4','5','6','7','8','9','*','#','A','B','C','D'};
static void linphone_core_dtmf_received (LinphoneCall *call, int dtmf) {
if (dtmf<0 || dtmf>15){
ms_warning("Bad dtmf value %i",dtmf);
return;
}
linphone_call_notify_dtmf_received(call, dtmf_tab[dtmf]);
}
#endif
/*This function is not static because used internally in linphone-daemon project*/
void _post_configure_audio_stream (AudioStream *st, LinphoneCore *lc, bool_t muted) {}
......@@ -259,41 +185,6 @@ void linphone_call_set_transfer_state (LinphoneCall *call, LinphoneCallState sta
void _linphone_call_set_new_params (LinphoneCall *call, const LinphoneCallParams *params) {}
#if 0
static int send_dtmf_handler (void *data, unsigned int revents) {
LinphoneCall *call = (LinphoneCall*)data;
/*By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO*/
if (linphone_core_get_use_rfc2833_for_dtmf(call->core)!=0 || linphone_core_get_use_info_for_dtmf(call->core)==0)
{
/* In Band DTMF */
if (call->audiostream){
audio_stream_send_dtmf(call->audiostream,*call->dtmf_sequence);
}
else
{
ms_error("Cannot send RFC2833 DTMF when we are not in communication.");
return FALSE;
}
}
if (linphone_core_get_use_info_for_dtmf(call->core)!=0){
/* Out of Band DTMF (use INFO method) */
sal_call_send_dtmf(call->op,*call->dtmf_sequence);
}
/*this check is needed because linphone_call_send_dtmf does not set the timer since its a single character*/
if (call->dtmfs_timer) {
memmove(call->dtmf_sequence, call->dtmf_sequence+1, strlen(call->dtmf_sequence));
}
/* continue only if the dtmf sequence is not empty*/
if (call->dtmf_sequence && *call->dtmf_sequence!='\0') {
return TRUE;
} else {
linphone_call_cancel_dtmfs(call);
return FALSE;
}
}
#endif
/* Internal version that does not play tone indication*/
int _linphone_call_pause (LinphoneCall *call) {
return 0;
......@@ -607,54 +498,25 @@ void linphone_call_zoom (LinphoneCall *call, float zoom_factor, float cx, float
}
LinphoneStatus linphone_call_send_dtmf (LinphoneCall *call, char dtmf) {
#if 0
if (!call){
ms_warning("linphone_call_send_dtmf(): invalid call, canceling DTMF.");
if (!call) {
ms_warning("linphone_call_send_dtmf(): invalid call, canceling DTMF");
return -1;
}
call->dtmf_sequence = &dtmf;
send_dtmf_handler(call,0);
call->dtmf_sequence = nullptr;
return 0;
#else
return 0;
#endif
return L_GET_CPP_PTR_FROM_C_OBJECT(call)->sendDtmf(dtmf);
}
LinphoneStatus linphone_call_send_dtmfs (LinphoneCall *call, const char *dtmfs) {
#if 0
if (!call){
ms_warning("linphone_call_send_dtmfs(): invalid call, canceling DTMF sequence.");
if (!call) {
ms_warning("linphone_call_send_dtmfs(): invalid call, canceling DTMF sequence");
return -1;
}
if (call->dtmfs_timer){
ms_warning("linphone_call_send_dtmfs(): a DTMF sequence is already in place, canceling DTMF sequence.");
return -2;
}
if (dtmfs) {
int delay_ms = lp_config_get_int(call->core->config,"net","dtmf_delay_ms",200);
call->dtmf_sequence = ms_strdup(dtmfs);
call->dtmfs_timer = sal_create_timer(call->core->sal, send_dtmf_handler, call, delay_ms, "DTMF sequence timer");
}
return 0;
#else
return 0;
#endif
return L_GET_CPP_PTR_FROM_C_OBJECT(call)->sendDtmfs(dtmfs);
}
void linphone_call_cancel_dtmfs (LinphoneCall *call) {
#if 0
/*nothing to do*/
if (!call || !call->dtmfs_timer) return;
sal_cancel_timer(call->core->sal, call->dtmfs_timer);
belle_sip_object_unref(call->dtmfs_timer);
call->dtmfs_timer = nullptr;
if (call->dtmf_sequence) {
ms_free(call->dtmf_sequence);
call->dtmf_sequence = nullptr;
}
#endif
if (!call)
return;
L_GET_CPP_PTR_FROM_C_OBJECT(call)->cancelDtmfs();
}
bool_t linphone_call_is_in_conference (const LinphoneCall *call) {
......
......@@ -36,6 +36,7 @@ public:
virtual void onCallSetTerminated () = 0;
virtual void onCallStateChanged (LinphoneCallState state, const std::string &message) = 0;
virtual void onCheckForAcceptation () = 0;
virtual void onDtmfReceived (char dtmf) = 0;
virtual void onIncomingCallStarted () = 0;
virtual void onIncomingCallToBeAdded () = 0;
virtual void onInfoReceived (const LinphoneInfoMessage *im) = 0;
......
......@@ -63,6 +63,7 @@ private:
void onCallSetTerminated () override;
void onCallStateChanged (LinphoneCallState state, const std::string &message) override;
void onCheckForAcceptation () override;
void onDtmfReceived (char dtmf) override;
void onIncomingCallStarted () override;
void onIncomingCallToBeAdded () override;
void onInfoReceived (const LinphoneInfoMessage *im) override;
......
......@@ -151,6 +151,11 @@ void CallPrivate::onCheckForAcceptation () {
bctbx_list_free(copy);
}
void CallPrivate::onDtmfReceived (char dtmf) {
L_Q();
linphone_call_notify_dtmf_received(L_GET_C_BACK_PTR(q), dtmf);
}
void CallPrivate::onIncomingCallStarted () {
L_Q();
linphone_core_notify_incoming_call(q->getCore()->getCCore(), L_GET_C_BACK_PTR(q));
......@@ -228,6 +233,11 @@ LinphoneStatus Call::acceptUpdate (const MediaSessionParams *msp) {
return static_cast<MediaSession *>(d->getActiveSession().get())->acceptUpdate(msp);
}
void Call::cancelDtmfs () {
L_D();
static_pointer_cast<MediaSession>(d->getActiveSession())->cancelDtmfs();
}
LinphoneStatus Call::decline (LinphoneReason reason) {
L_D();
return d->getActiveSession()->decline(reason);
......@@ -258,6 +268,16 @@ LinphoneStatus Call::resume () {
return static_cast<MediaSession *>(d->getActiveSession().get())->resume();
}
LinphoneStatus Call::sendDtmf (char dtmf) {
L_D();
return static_pointer_cast<MediaSession>(d->getActiveSession())->sendDtmf(dtmf);
}
LinphoneStatus Call::sendDtmfs (const std::string &dtmfs) {
L_D();
return static_pointer_cast<MediaSession>(d->getActiveSession())->sendDtmfs(dtmfs);
}
void Call::sendVfuRequest () {
L_D();
static_cast<MediaSession *>(d->getActiveSession().get())->sendVfuRequest();
......
......@@ -44,12 +44,15 @@ public:
LinphoneStatus accept (const MediaSessionParams *msp = nullptr);
LinphoneStatus acceptEarlyMedia (const MediaSessionParams *msp = nullptr);
LinphoneStatus acceptUpdate (const MediaSessionParams *msp);
void cancelDtmfs ();
LinphoneStatus decline (LinphoneReason reason);
LinphoneStatus decline (const LinphoneErrorInfo *ei);
void oglRender () const;
LinphoneStatus pause ();
LinphoneStatus redirect (const std::string &redirectUri);
LinphoneStatus resume ();
LinphoneStatus sendDtmf (char dtmf);
LinphoneStatus sendDtmfs (const std::string &dtmfs);
void sendVfuRequest ();
void startRecording ();
void stopRecording ();
......
......@@ -164,6 +164,12 @@ void Conference::onCheckForAcceptation (const shared_ptr<const CallSession> &ses
d->callListener->onCheckForAcceptation();
}
void Conference::onDtmfReceived (const std::shared_ptr<const CallSession> &session, char dtmf) {
L_D();
if (d->callListener)
d->callListener->onDtmfReceived(dtmf);
}
void Conference::onIncomingCallSessionStarted (const shared_ptr<const CallSession> &session) {
L_D();
if (d->callListener)
......
......@@ -73,6 +73,7 @@ private:
void onCallSessionSetTerminated (const std::shared_ptr<const CallSession> &session) override;
void onCallSessionStateChanged (const std::shared_ptr<const CallSession> &session, LinphoneCallState state, const std::string &message) override;
void onCheckForAcceptation (const std::shared_ptr<const CallSession> &session) override;
void onDtmfReceived (const std::shared_ptr<const CallSession> &session, char dtmf) override;
void onIncomingCallSessionStarted (const std::shared_ptr<const CallSession> &session) override;
void onInfoReceived (const std::shared_ptr<const CallSession> &session, const LinphoneInfoMessage *im) override;
void onEncryptionChanged (const std::shared_ptr<const CallSession> &session, bool activated, const std::string &authToken) override;
......
......@@ -37,6 +37,7 @@ public:
virtual void onCallSessionSetTerminated (const std::shared_ptr<const CallSession> &session) = 0;
virtual void onCallSessionStateChanged (const std::shared_ptr<const CallSession> &session, LinphoneCallState state, const std::string &message) = 0;
virtual void onCheckForAcceptation (const std::shared_ptr<const CallSession> &session) = 0;
virtual void onDtmfReceived (const std::shared_ptr<const CallSession> &session, char dtmf) = 0;
virtual void onIncomingCallSessionStarted (const std::shared_ptr<const CallSession> &session) = 0;
virtual void onInfoReceived (const std::shared_ptr<const CallSession> &session, const LinphoneInfoMessage *im) = 0;
......
......@@ -132,14 +132,6 @@ void CallSessionPrivate::setState(LinphoneCallState newState, const string &mess
break;
}
if (newState != LinphoneCallStreamsRunning) {
#if 0 // TODO
if (call->dtmfs_timer!=NULL){
/*cancelling DTMF sequence, if any*/
linphone_call_cancel_dtmfs(call);
}
#endif
}
if (message.empty()) {
lError() << "You must fill a reason when changing call state (from " <<
linphone_call_state_to_string(prevState) << " to " << linphone_call_state_to_string(state) << ")";
......
......@@ -44,10 +44,12 @@ public:
void accepted () override;
void ackReceived (LinphoneHeaders *headers) override;
void dtmfReceived (char dtmf);
bool failure () override;
void pausedByRemote ();
void remoteRinging () override;
void resumed ();
void telephoneEventReceived (int event);
void terminated () override;
void updated (bool isUpdate);
void updating (bool isUpdate) override;
......@@ -93,14 +95,14 @@ public:
private:
static OrtpJitterBufferAlgorithm jitterBufferNameToAlgo (const std::string &name);
#ifdef VIDEO_ENABLED
static void videoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args);
#endif // ifdef VIDEO_ENABLED
#ifdef TEST_EXT_RENDERER
static void extRendererCb (void *userData, const MSPicture *local, const MSPicture *remote);
#endif // ifdef TEST_EXT_RENDERER
#ifdef VIDEO_ENABLED
static void videoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args);
#endif // ifdef VIDEO_ENABLED
#ifdef TEST_EXT_RENDERER
static void extRendererCb (void *userData, const MSPicture *local, const MSPicture *remote);
#endif // ifdef TEST_EXT_RENDERER
static void realTimeTextCharacterReceived (void *userData, MSFilter *f, unsigned int id, void *arg);
static int sendDtmf (void *data, unsigned int revents);
static float aggregateQualityRatings (float audioRating, float videoRating);
std::shared_ptr<Participant> getMe () const;
......@@ -241,10 +243,11 @@ private:
void refreshSockets ();
void reinviteToRecoverFromConnectionLoss () override;
#ifdef VIDEO_ENABLED
void videoStreamEventCb (const MSFilter *f, const unsigned int eventId, const void *args);
#endif // ifdef VIDEO_ENABLED
#ifdef VIDEO_ENABLED
void videoStreamEventCb (const MSFilter *f, const unsigned int eventId, const void *args);
#endif // ifdef VIDEO_ENABLED
void realTimeTextCharacterReceived (MSFilter *f, unsigned int id, void *arg);
int sendDtmf ();
void stunAuthRequestedCb (const char *realm, const char *nonce, const char **username, const char **password, const char **ha1);
......@@ -283,6 +286,9 @@ private:
// The address family to prefer for RTP path, guessed from signaling path.
int af;
std::string dtmfSequence;
belle_sip_source_t *dtmfTimer = nullptr;
std::string mediaLocalIp;
PortConfig mediaPorts[SAL_MEDIA_DESCRIPTION_MAX_STREAMS];
bool needMediaLocalIpRefresh = false;
......
......@@ -28,6 +28,7 @@
#include "conference/params/media-session-params-p.h"
#include "conference/session/media-session.h"
#include "core/core.h"
#include "sal/sal.h"
#include "utils/payload-type-handler.h"
#include "logger/logger.h"
......@@ -194,6 +195,12 @@ void MediaSessionPrivate::ackReceived (LinphoneHeaders *headers) {
}
}
void MediaSessionPrivate::dtmfReceived (char dtmf) {
L_Q();
if (listener)
listener->onDtmfReceived(q->getSharedFromThis(), dtmf);
}
bool MediaSessionPrivate::failure () {
L_Q();
const SalErrorInfo *ei = op->get_error_info();
......@@ -323,6 +330,15 @@ void MediaSessionPrivate::resumed () {
acceptUpdate(nullptr, LinphoneCallStreamsRunning, "Connected (streams running)");
}
void MediaSessionPrivate::telephoneEventReceived (int event) {
static char dtmfTab[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D' };
if ((event < 0) || (event > 15)) {
lWarning() << "Bad dtmf value " << event;
return;
}
dtmfReceived(dtmfTab[event]);
}
void MediaSessionPrivate::terminated () {
stopStreams();
CallSessionPrivate::terminated();
......@@ -577,6 +593,11 @@ void MediaSessionPrivate::realTimeTextCharacterReceived (void *userData, MSFilte
msp->realTimeTextCharacterReceived(f, id, arg);
}
int MediaSessionPrivate::sendDtmf (void *data, unsigned int revents) {
MediaSession *session = reinterpret_cast<MediaSession *>(data);
return session->getPrivate()->sendDtmf();
}
// -----------------------------------------------------------------------------
float MediaSessionPrivate::aggregateQualityRatings (float audioRating, float videoRating) {
......@@ -607,6 +628,8 @@ void MediaSessionPrivate::setState (LinphoneCallState newState, const string &me
L_Q();
/* Take a ref on the session otherwise it might get destroyed during the call to setState */
shared_ptr<CallSession> sessionRef = q->getSharedFromThis();
if ((newState != state) && (newState != LinphoneCallStreamsRunning))
q->cancelDtmfs();
CallSessionPrivate::setState(newState, message);
updateReportingCallState();
}
......@@ -2194,9 +2217,7 @@ void MediaSessionPrivate::handleStreamEvents (int streamIndex) {
if (ms)
handleIceEvents(ev);
} else if (evt == ORTP_EVENT_TELEPHONE_EVENT) {
#if 0
linphone_core_dtmf_received(call, evd->info.telephone_event);
#endif
telephoneEventReceived(evd->info.telephone_event);
} else if (evt == ORTP_EVENT_NEW_VIDEO_BANDWIDTH_ESTIMATION_AVAILABLE) {
lInfo() << "Video bandwidth estimation is " << (int)(evd->info.video_bandwidth_available / 1000.) << " kbit/s";
// TODO
......@@ -4016,6 +4037,34 @@ void MediaSessionPrivate::realTimeTextCharacterReceived (MSFilter *f, unsigned i
}
}
int MediaSessionPrivate::sendDtmf () {
L_Q();
LinphoneCore *lc = q->getCore()->getCCore();
// By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO
if (linphone_core_get_use_rfc2833_for_dtmf(lc) || !linphone_core_get_use_info_for_dtmf(lc)) {
// In Band DTMF
if (audioStream)
audio_stream_send_dtmf(audioStream, dtmfSequence.front());
else {
lError() << "Cannot send RFC2833 DTMF when we are not in communication";
return FALSE;
}
}
if (linphone_core_get_use_info_for_dtmf(lc)) {
// Out of Band DTMF (use INFO method)
op->send_dtmf(dtmfSequence.front());
}
dtmfSequence.erase(0, 1);
// Continue only if the dtmf sequence is not empty
if (!dtmfSequence.empty())
return TRUE;
else {
q->cancelDtmfs();
return FALSE;
}
}
// -----------------------------------------------------------------------------
void MediaSessionPrivate::stunAuthRequestedCb (const char *realm, const char *nonce, const char **username, const char **password, const char **ha1) {
......@@ -4094,6 +4143,7 @@ MediaSession::MediaSession (const shared_ptr<Core> &core, shared_ptr<Participant
MediaSession::~MediaSession () {
L_D();
cancelDtmfs();
if (d->audioStats)
linphone_call_stats_unref(d->audioStats);
if (d->videoStats)
......@@ -4176,7 +4226,16 @@ LinphoneStatus MediaSession::acceptUpdate (const MediaSessionParams *msp) {
return CallSession::acceptUpdate(msp);
}
// -----------------------------------------------------------------------------
void MediaSession::cancelDtmfs () {
L_D();
if (!d->dtmfTimer)
return;
getCore()->getCCore()->sal->cancel_timer(d->dtmfTimer);
belle_sip_object_unref(d->dtmfTimer);
d->dtmfTimer = nullptr;
d->dtmfSequence.clear();
}
void MediaSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg, SalCallOp *op, const Address &from, const Address &to) {
L_D();
......@@ -4316,6 +4375,27 @@ LinphoneStatus MediaSession::resume () {
return 0;
}
LinphoneStatus MediaSession::sendDtmf (char dtmf) {
L_D();
d->dtmfSequence = dtmf;
d->sendDtmf();
return 0;
}
LinphoneStatus MediaSession::sendDtmfs (const std::string &dtmfs) {
L_D();
if (d->dtmfTimer) {
lWarning() << "MediaSession::sendDtmfs(): a DTMF sequence is already in place, canceling DTMF sequence";
return -2;
}
if (!dtmfs.empty()) {
int delayMs = lp_config_get_int(linphone_core_get_config(getCore()->getCCore()), "net", "dtmf_delay_ms", 200);
d->dtmfSequence = dtmfs;
d->dtmfTimer = getCore()->getCCore()->sal->create_timer(MediaSessionPrivate::sendDtmf, this, delayMs, "DTMF sequence timer");
}
return 0;
}
void MediaSession::sendVfuRequest () {
#ifdef VIDEO_ENABLED
L_D();
......
......@@ -45,12 +45,15 @@ public:
LinphoneStatus accept (const MediaSessionParams *msp = nullptr);
LinphoneStatus acceptEarlyMedia (const MediaSessionParams *msp = nullptr);
LinphoneStatus acceptUpdate (const MediaSessionParams *msp);
void cancelDtmfs ();
void configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg, SalCallOp *op, const Address &from, const Address &to) override;
void initiateIncoming () override;
bool initiateOutgoing () override;
void iterate (time_t currentRealTime, bool oneSecondElapsed) override;
LinphoneStatus pause ();
LinphoneStatus resume ();
LinphoneStatus sendDtmf (char dtmf);
LinphoneStatus sendDtmfs (const std::string &dtmfs);
void sendVfuRequest ();
void startIncomingNotification () override;
int startInvite (const Address *destination, const std::string &subject = "", const Content *content = nullptr) override;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment