[pjsip] PJSIP 1.6 vs. iOS4 - a (limited) solution

Patricia Zerbe zerbe.patricia at googlemail.com
Sat Aug 21 15:52:55 CDT 2010


After fighting with PJSIP 1.6, my VoIP app and iOS 4 I found a
possible solution I like to share with you. But at first I want to
thank Samuel and Andrea for providing their fixes of the UDP socket
handling, both snippets work fine and solved a dead lock I faced
during my attempt to re-create the broken UDP socket handles when iOS4
wakes the previously suspended app.

Before I outline my solution I have to say that this only works for
SIP servers that support signaling SIP events over TCP connections. So
your iOS4 app has to setup a tcp_transport.  Something like that:

	pjsua_transport_config tcpConfig;
	pjsua_transport_id idTransport;
	pjsua_transport_config_default (&tcpConfig);
	tcpConfig.port = signalingPort;  // default 5060
	status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
									&tcpConfig, &idTransport);
	if (status != PJ_SUCCESS) {
		// error handling
	….

I took a simple and 'brutal' approach to solve the UDP socket handle problem.

a) Configure the TCP sockets to be managed by iOS4 so iOS4 wakes the
app when TCP traffic occurs.

b) When an incoming call is signaled over the TCP connection
	1. Unregister all codecs
	2. Terminate the worker thread
	3. Re-init every previously allocated UDP (RTP, RTCP) socket handle
	4. Re-register all codecs
	4. Restart the worker thread
 	

Part a) was the easy one. The way how to configure a TCP socket to
work in background is well documented but I include this snippet for
the sake of completeness

void prepareTcpSocketForBg (long sockhandle) {
	
	CFReadStreamRef readStream;
	CFWriteStreamRef writeStream;
	CFStreamClientContext myContext = {0, NULL, NULL, NULL, NULL};
	
	
	CFStreamCreatePairWithSocket(kCFAllocatorDefault,
								 sockhandle,
								 &readStream,
								 &writeStream);
	
	CFReadStreamSetProperty(readStream,
kCFStreamNetworkServiceType,kCFStreamNetworkServiceTypeVoIP);

	CFWriteStreamSetProperty(writeStream,
kCFStreamNetworkServiceType,kCFStreamNetworkServiceTypeVoIP);
	
	CFReadStreamOpen(readStream);	
	CFWriteStreamOpen(writeStream);
}


Part b) was the nasty one to implement because at first I had no idea
how to do that. And my knowledge of BSD socket programming has to be
called rusty, at least.
Finally I came up with that:

void reInitRtpTransport (pjsua_transport_config* rtpConfig) {

	deinitAllCodecs ();
	quitAllWorkerThreads ();
	reInitSockets (rtpConfig);
	initAllCodecs ();
	restartAllWorkerThreads ();
}

The tasks to unregister and register the codes are straight forward.
The same is true for handling the termination and restart of the
worker thread(s). OK, without the two fixes provided by Andrea and
Samuel for UDP socket handling I would not be in the position to write
this post. So, again a big Thank You to both of you!

Basically I copied the following stuff from various parts in the PJSIP project:


//
// in pjsua_media.c: deinitAllCodecs() and initAllCodecs()
//


void deinitAllCodecs() {
#if PJMEDIA_HAS_SPEEX_CODEC
	pjmedia_codec_speex_deinit();
#endif	
#if PJMEDIA_HAS_ILBC_CODEC
	pjmedia_codec_ilbc_deinit();
#endif
#if PJMEDIA_HAS_GSM_CODEC
	pjmedia_codec_gsm_deinit();
#endif
#if PJMEDIA_HAS_G711_CODEC
	pjmedia_codec_g711_deinit ();
#endif
#if PJMEDIA_HAS_G722_CODEC
	pjmedia_codec_g722_deinit();
#endif	
#if PJMEDIA_HAS_INTEL_IPP
	pjmedia_codec_ipp_deinit();
#endif	
#if PJMEDIA_HAS_PASSTHROUGH_CODECS
	pjmedia_codec_passthrough_deinit();
#endif	
#if PJMEDIA_HAS_G7221_CODEC
	pjmedia_codec_g7221_deinit();
#endif
#if PJMEDIA_HAS_L16_CODEC
	pjmedia_codec_l16_deinit();
#endif
	
}

void initAllCodecs () {
	
   	pj_status_t status;

	/* Register all codecs */
	
#if PJMEDIA_HAS_SPEEX_CODEC
    /* Register speex. */
    status = pjmedia_codec_speex_init(pjsua_var.med_endpt,
									  0,
									  pjsua_var.media_cfg.quality,
									  -1);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing Speex codec",
					 status);
		return status;
    }
	
    /* Set speex/16000 to higher priority*/
    codec_id = pj_str("speex/16000");
    pjmedia_codec_mgr_set_codec_priority(
										 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
										 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
	
    /* Set speex/8000 to next higher priority*/
    codec_id = pj_str("speex/8000");
    pjmedia_codec_mgr_set_codec_priority(
										 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
										 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
	
	
	
#endif /* PJMEDIA_HAS_SPEEX_CODEC */
	
#if PJMEDIA_HAS_ILBC_CODEC
    /* Register iLBC. */
    status = pjmedia_codec_ilbc_init( pjsua_var.med_endpt,
									 pjsua_var.media_cfg.ilbc_mode);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing iLBC codec",
					 status);
		return status;
    }
#endif /* PJMEDIA_HAS_ILBC_CODEC */
	
#if PJMEDIA_HAS_GSM_CODEC
    /* Register GSM */
	pjmedia_codec_gsm_deinit();
    status = pjmedia_codec_gsm_init(pjsua_var.med_endpt);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing GSM codec",
					 status);
		//		return status;
    }
#endif /* PJMEDIA_HAS_GSM_CODEC */
	
#if PJMEDIA_HAS_G711_CODEC
    /* Register PCMA and PCMU */
    status = pjmedia_codec_g711_init(pjsua_var.med_endpt);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing G711 codec",
					 status);
		//		return status;
    }
#endif	/* PJMEDIA_HAS_G711_CODEC */
	
#if PJMEDIA_HAS_G722_CODEC
    status = pjmedia_codec_g722_init( pjsua_var.med_endpt );
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing G722 codec",
					 status);
		return status;
    }
#endif  /* PJMEDIA_HAS_G722_CODEC */
	
#if PJMEDIA_HAS_INTEL_IPP
    /* Register IPP codecs */
	pjmedia_codec_ipp_deinit();
    status = pjmedia_codec_ipp_init(pjsua_var.med_endpt);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing IPP codecs",
					 status);
		return status;
    }
	
#endif /* PJMEDIA_HAS_INTEL_IPP */
	
#if PJMEDIA_HAS_PASSTHROUGH_CODECS
    /* Register passthrough codecs */
    {
		unsigned aud_idx;
		unsigned ext_fmt_cnt = 0;
		pjmedia_format ext_fmts[32];
		pjmedia_codec_passthrough_setting setting;
		
		/* List extended formats supported by audio devices */
		for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
			pjmedia_aud_dev_info aud_info;
			unsigned i;
			
			status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
			if (status != PJ_SUCCESS) {
				pjsua_perror(THIS_FILE, "Error querying audio device info",
							 status);
				return status;
			}
			
			/* Collect extended formats supported by this audio device */
			for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
				unsigned j;
				pj_bool_t is_listed = PJ_FALSE;
				
				/* See if this extended format is already in the list */
				for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
					if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
						ext_fmts[j].bitrate == aud_info.ext_fmt[i].bitrate)
					{
						is_listed = PJ_TRUE;
					}
				}
				
				/* Put this format into the list, if it is not in the list */
				if (!is_listed)
					ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
				
				pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
			}
		}
		
		/* Init the passthrough codec with supported formats only */
		setting.fmt_cnt = ext_fmt_cnt;
		setting.fmts = ext_fmts;
		setting.ilbc_mode = cfg->ilbc_mode;
		status = pjmedia_codec_passthrough_init2(pjsua_var.med_endpt, &setting);
		if (status != PJ_SUCCESS) {
			pjsua_perror(THIS_FILE, "Error initializing passthrough codecs",
						 status);
			return status;
		}
    }
#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
	
#if PJMEDIA_HAS_G7221_CODEC
    /* Register G722.1 codecs */
    status = pjmedia_codec_g7221_init(pjsua_var.med_endpt);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing G722.1 codec",
					 status);
		return status;
    }
#endif /* PJMEDIA_HAS_G7221_CODEC */
	
#if PJMEDIA_HAS_L16_CODEC
    /* Register L16 family codecs, but disable all */
    status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0);
    if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
					 status);
		return status;
    }
	
    /* Disable ALL L16 codecs */
    codec_id = pj_str("L16");
    pjmedia_codec_mgr_set_codec_priority(
										 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
										 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
	
#endif	/* PJMEDIA_HAS_L16_CODEC */
	
}


//
// in pjsua_core.c: quitAllWorkerThreads() and restartAllWorkerThreads()
//

void	quitAllWorkerThreads () {
    int i;
	
    /* Signal threads to quit: */
    pjsua_var.thread_quit_flag = 1;
	
    /* Wait worker threads to quit: */
    for (i=0; i<(int)pjsua_var.ua_cfg.thread_cnt; ++i) {
		
		if (pjsua_var.thread[i]) {
			pj_thread_join(pjsua_var.thread[i]);
			pj_thread_destroy(pjsua_var.thread[i]);
			pjsua_var.thread[i] = NULL;
		}
    }
}

void	restartAllWorkerThreads () {
    int i;
    pj_status_t status;

	pjsua_var.thread_quit_flag = FALSE;		
	if (pjsua_var.ua_cfg.thread_cnt > PJ_ARRAY_SIZE(pjsua_var.thread))
		pjsua_var.ua_cfg.thread_cnt = PJ_ARRAY_SIZE(pjsua_var.thread);
		
	for (i=0; i<pjsua_var.ua_cfg.thread_cnt; ++i) {
			
		status = pj_thread_create(pjsua_var.pool, "pjsua", &worker_thread,
NULL, 0, 0, &pjsua_var.thread[i]);
		if (status != PJ_SUCCESS) {
			pjsua_perror(THIS_FILE, "Error re-starting worker thread", status);

			return;
		}
	}
}


And now it gets ugly...

//
// in pjsua_media.c: reInitSockets()
//

void reInitSockets (pjsua_transport_config *cfg) {
    unsigned i;
	
    /* Create each media transport */
    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
		if (pjsua_var.calls[i].med_tp != NULL)
		{
			
			PJ_LOG(5,(THIS_FILE,
					  "reInitSockets: 're-socket-ing' call #%d",
					  i
					  ));
			reInitSingleSocket (pjsua_var.calls[i].med_tp, cfg);
		}
	}
}

... and it gets even worse

//
// in transport_udp.c: reInitSingleSocket()
//
/// copied from ioqueue_select.c
#include "ioqueue_common_abs.h"

// we need access to socket handle stored in pj_ioqueue_key_t.fd
struct pj_ioqueue_key_t
{
    DECLARE_COMMON_KEY
};


void reInitSingleSocket (pjmedia_transport *tp, pjsua_transport_config *cfg) {
	struct transport_udp	*callTp = (struct transport_udp*) tp;
    pj_status_t status;
	pj_sock_t sockRtp;
	pj_sock_t sockRtcp;
	
	if (callTp->base.type != PJMEDIA_TRANSPORT_TYPE_UDP)
		return;
	
	struct pj_ioqueue_key_t	*key = (struct pj_ioqueue_key_t	*)callTp->rtp_key;
	
				
	PJ_LOG(5,(THIS_FILE,
			  "reInitSingleSocket: previous RTP socket = %d",
			  callTp->rtp_sock
			  ));
	
	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sockRtp);
	if (status != PJ_SUCCESS) {
		PJ_LOG(5,(THIS_FILE,
				  "reInitSingleSocket: failed to create RTP socket"));
		return;
	}
	
	/* Apply QoS to RTP socket, if specified */
	status = pj_sock_apply_qos2(sockRtp, cfg->qos_type,
								&cfg->qos_params,
								2, THIS_FILE, "RTP socket");
	
	/* Bind RTP socket */
	status = pj_sock_bind_in(sockRtp,
pj_ntohl(callTp->rtp_addr_name.ipv4.sin_addr.s_addr),
callTp->rtp_addr_name.ipv4.sin_port);
	if (status != PJ_SUCCESS) {
		pjsua_perror(THIS_FILE, "reInitSingleSocket: bind() error", status);

		PJ_LOG(5,(THIS_FILE,
				  "reInitSockets: failed to bind RTP socket %d to port %d",
				  sockRtp,
				  callTp->rtp_addr_name.ipv4.sin_port
				  ));
		pj_sock_close(sockRtp);
		return;
	}
	callTp->rtp_sock = sockRtp;
	key->fd = sockRtp;
	
	key = (struct pj_ioqueue_key_t	*)callTp->rtcp_key;
	
	PJ_LOG(5,(THIS_FILE,
			  "reInitSingleSocket: previous RTCP socket = %d",
			  callTp->rtcp_sock
			  ));
	
	
	status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sockRtcp);
	if (status != PJ_SUCCESS) {
		PJ_LOG(5,(THIS_FILE,
				  "reInitSingleSocket: failed to create RTCP socket"));
		return;
	}
	
	/* Apply QoS to RTP socket, if specified */
	status = pj_sock_apply_qos2(sockRtcp, cfg->qos_type,
								&cfg->qos_params,
								2, THIS_FILE, "RTCP socket");
	
	/* Bind RCTP socket */
	status = pj_sock_bind_in(sockRtcp,
pj_ntohl(callTp->rtcp_addr_name.ipv4.sin_addr.s_addr),
callTp->rtcp_addr_name.ipv4.sin_port);
	if (status != PJ_SUCCESS) {
		PJ_LOG(5,(THIS_FILE,
				  "reInitSingleSocket: failed to bind RCTP socket %d to port %d",
				  sockRtcp,
				  callTp->rtp_addr_name.ipv4.sin_port
				  ));
		pj_sock_close(sockRtcp);
		return;
	}
	
	callTp->rtp_sock = sockRtcp;
	key->fd = sockRtcp;
}


Finally I owe you one final detail where and when to call reInitRtpTransport().
I have implemented that call in a notification handler that runs on
the main thread. The parameter rtpConfig holds the address of the RTP
transport config variable I used to create the initial RTP transport.


So, basically that's all, folks. HTH

Trisha :)

PS. You can reach me at work: p (dot) zerbe (at) avm (dot) de



More information about the pjsip mailing list