Welcome to the 5th part of this tutorial series!
In the previous tutorial, we have created an iOS project, and made pjsip functions ready to be called. In today’s tutorial, we are going to actually call those pjsip functions from our iOS app.
In case you’re new to this series, here is a quick recap of previous tutorials:
- Part 1: How to compile pjsip and run the embeded demo on your iOS device
- Part 2: How to set up your own VoIP server via Kamailio, and actually make VoIP call between an iOS device and a Mac.
- Part 3: A brief introduction of pjsip API, by digging into the source code of a very simple Mac VoIP program.
- Part 4: How to add the pjsip libraries and header files into your own iOS project, so that you can call pjsip API from your app.
You could download the source code of this project (starting from the 4th tutorial) on Github here. Each commit of the project, is representing one step of the tutorial.
Adding XCPjsua.c and XCPjsua.h
As we mentioned before, the interfaces of pjsip functions are in C. So in order to call pjsip methods, normally, we would create a C file to encapsulate invocation of pjsip methods, and then expose the methods of this C file via a header file.
Create a new Header File, and name it XCPjsua.h
Then, create a new C File, and name it XCPjsua.c
Your project will look like this afterwards:
Starting pjsua
In the XCPjsua.h, add the following declaration of startPjsua() method:
1
2
3
4
5
6
7
8
9
|
/**
* Initialize and start pjsua.
*
* @param sipUser the sip username to be used for register
* @param sipDomain the domain of the sip register server
*
* @return When successful, returns 0.
*/
int startPjsip(char *sipUser, char* sipDomain);
|
In the XCPjsua.c, add the following code to implement this startPjsua() method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
#include
#define THIS_FILE "XCPjsua.c"
static pjsua_acc_id acc_id;
const size_t MAX_SIP_ID_LENGTH = 50;
const size_t MAX_SIP_REG_URI_LENGTH = 50;
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata);
static void on_call_state(pjsua_call_id call_id, pjsip_event *e);
static void on_call_media_state(pjsua_call_id call_id);
static void error_exit(const char *title, pj_status_t status);
int startPjsip(char *sipUser, char* sipDomain)
{
pj_status_t status;
// Create pjsua first
status = pjsua_create();
if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
// Init pjsua
{
// Init the config structure
pjsua_config cfg;
pjsua_config_default (&cfg);
cfg.cb.on_incoming_call = &on_incoming_call;
cfg.cb.on_call_media_state = &on_call_media_state;
cfg.cb.on_call_state = &on_call_state;
// Init the logging config structure
pjsua_logging_config log_cfg;
pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;
// Init the pjsua
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
}
// Add UDP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5080;
// Add TCP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
}
// Add TCP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5080;
// Add TCP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
}
// Initialization is done, now start pjsua
status = pjsua_start();
if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);
// Register the account on local sip server
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
char sipId[MAX_SIP_ID_LENGTH];
sprintf(sipId, "sip:%s@%s", sipUser, sipDomain);
cfg.id = pj_str(sipId);
char regUri[MAX_SIP_REG_URI_LENGTH];
sprintf(regUri, "sip:%s", sipDomain);
cfg.reg_uri = pj_str(regUri);
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding account", status);
}
return 0;
}
/* Callback called by the library upon receiving incoming call */
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
pjsip_rx_data *rdata)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(acc_id);
PJ_UNUSED_ARG(rdata);
pjsua_call_get_info(call_id, &ci);
PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!",
(int)ci.remote_info.slen,
ci.remote_info.ptr));
/* Automatically answer incoming calls with 200/OK */
pjsua_call_answer(call_id, 200, NULL, NULL);
}
/* Callback called by the library when call's state has changed */
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
(int)ci.state_text.slen,
ci.state_text.ptr));
}
/* Callback called by the library when call's media state has changed */
static void on_call_media_state(pjsua_call_id call_id)
{
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
// When media is active, connect call to sound device.
pjsua_conf_connect(ci.conf_slot, 0);
pjsua_conf_connect(0, ci.conf_slot);
}
}
/* Display error and exit application */
static void error_exit(const char *title, pj_status_t status)
{
pjsua_perror(THIS_FILE, title, status);
pjsua_destroy();
exit(1);
}
|
This code is almost the same as what we’ve discussed in the 3rd post. If you don’t understand any part of it, you could probably find your answer in that post.
We have 2 main differences here:
- We made the account id – “pjsua_acc_id acc_id” – a static variable. Because we’ll need this acc_id to make VoIP call later.
- We parameterized the sipUser and the sipDomain, so that we could easily connect to different VoIP servers in differnt userNames.
Make VoIP call and end VoIP call
Declare the makeCall() and endCall() in the XCPjsua.h
1
2
3
4
5
6
7
8
9
10
11
|
/**
* Make VoIP call.
*
* @param destUri the uri of the receiver, something like "sip:192.168.43.106:5080"
*/
void makeCall(char* destUri);
/**
* End ongoing VoIP calls
*/
void endCall();
|
And add the following code in XCPjsua.c to implement them:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void makeCall(char* destUri)
{
pj_status_t status;
pj_str_t uri = pj_str(destUri);
status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL);
if (status != PJ_SUCCESS) error_exit("Error making call", status);
}
void endCall()
{
pjsua_call_hangup_all();
}
|
The code itself should be quite straight forward. We simply make use of thepjsua_call_make_call() and the pjsua_call_hangup_all() to make call and end call.
The acc_id being used in pjsua_call_make_call() is the global variable we declared before.
Define PJ_AUTOCONF
You should be able to compile and run this app in the simulator now. (But 64-bit simulator is not supported yet, becasue we didn’t include the x86-64 architecture in our fat pjsip library. We only included the i386 architecture, which is for 32-bit simulator).
However, if you try to build and run it on a real device, you’ll get compile error like the following:
User Defined Issues: "Please specify target machine."
Basically, it’s asking you to specify a target architecture. But we could let pjsip do it automatically for you:
Go to the “Build Settings”, and add PJ_AUTOCONF macro in the Preprocessor Macros field.
Try again, and you’ll be able to run this project on your own iPhone!
Initialize pjsip and actually make VoIP call
After all these hard prepartion work, it’s super easy now to initialize pjsip and make our VoIP call.
Start the local Kamailio server on your Mac, and run the mac receiver for receiving the incoming call (If you forgot how to do this, you could get more info in the second tutorial). And then, check the IP of your Mac, which would be the IP of your SIP server, and also the IP of your receiver.
Next, add this import statement at the beginning of your XCAppDelegate.m:
And add the following code in the didFinishLaunchingWithOptions method ofXCAppDelegate.m. In my case, the IP of my Mac is 192.168.43.106, you should change it according to your own IP.
1
2
3
4
5
6
7
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// You should change 192.168.43.106 to the IP of your own Mac
startPjsip("iPhone", "192.168.43.106");
makeCall("sip:192.168.43.106:5080");
return YES;
}
|
The port being used here – 5080 – is the port the receiver program listening to.
Now, go ahead to compile and run your app on your iPhone. When the app launches, it will register itself on your SIP server, and make call to the receiver program. You will then be able to hear your voice from your iPhone on your Mac and vice versa!