PJSIP là một thư viện mã nguồn mở mạnh mẽ và có khả năng di chuyển, được thiết kế để phát triển các ứng dụng VoIP và nhắn tin tức thời dựa trên giao thức SIP. Dưới đây là hướng dẫn cơ bản để bắt đầu với PJSIP.
1. Giới thiệu về PJSIP
PJSIP là một thư viện giao tiếp đa phương tiện viết bằng ngôn ngữ C, với những tính năng nổi bật như:
- Hỗ trợ các giao thức SIP, SDP, RTP, STUN, TURN và ICE
- Nhỏ gọn và hiệu suất cao
- Có thể chạy trên hầu hết các nền tảng (Windows, Linux, macOS, hệ thống nhúng,...)
- Cung cấp cả API cấp cao và cấp thấp
2. Cài đặt PJSIP
Cài đặt trên Linux
bash
# Cài đặt các gói phụ thuộc
sudo apt-get install build-essential python3-dev libssl-dev libasound2-dev
# Tải mã nguồn
wget https://www.pjsip.org/release/2.13/pjproject-2.13.tar.bz2
tar -xjvf pjproject-2.13.tar.bz2
cd pjproject-2.13
# Biên dịch và cài đặt
./configure
make dep && make
sudo make install
Cài đặt trên Windows
- Tải và giải nén gói mã nguồn
- Mở file
pjproject-vs14.slnbằng Visual Studio (tùy phiên bản VS) - Biên dịch dự án pjsua
3. Khái niệm cơ bản
- Endpoint (pjsua): Đối tượng quản lý trung tâm trong ứng dụng PJSIP
- Account: Cấu hình tài khoản SIP
- Call: Đối tượng cuộc gọi
- Media: Luồng dữ liệu âm thanh/video
4. Ứng dụng đầu tiên với PJSIP
Sau đây là ví dụ đơn giản về một chương trình PJSIP:
c
#include <pjsua-lib/pjsua.h>
#define DOMAIN "sip.example.com"
#define USER "1001"
#define PASS "password"
/* Hàm callback khi nhận cuộc gọi đến */
static void incoming_call_handler(pjsua_acc_id acc_id, pjsua_call_id call_id,
pjsip_rx_data *rdata) {
pjsua_call_answer(call_id, 200, NULL, NULL);
}
/* Hàm callback khi trạng thái cuộc gọi thay đổi */
static void call_state_handler(pjsua_call_id call_id, pjsip_event *e) {
pjsua_call_info info;
pjsua_call_get_info(call_id, &info);
PJ_LOG(3, ("", "Call %d state=%.*s", call_id,
(int)info.state_text.slen, info.state_text.ptr));
}
int main() {
pjsua_acc_id acc_id;
pj_status_t status;
// Khởi tạo điểm cuối PJSIP
status = pjsua_create();
if (status != PJ_SUCCESS) {
printf("Lỗi tạo điểm cuối PJSIP\n");
return 1;
}
// Thiết lập cấu hình
pjsua_config config;
pjsua_config_default(&config);
config.cb.on_incoming_call = &incoming_call_handler;
config.cb.on_call_state = &call_state_handler;
// Cấu hình nhật ký
pjsua_logging_config log_config;
pjsua_logging_config_default(&log_config);
log_config.console_level = 4;
// Khởi tạo
status = pjsua_init(&config, &log_config, NULL);
if (status != PJ_SUCCESS) {
printf("Lỗi khởi tạo PJSIP\n");
return 1;
}
// Tạo giao thức UDP
pjsua_transport_config udp_config;
pjsua_transport_config_default(&udp_config);
udp_config.port = 5060;
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &udp_config, NULL);
if (status != PJ_SUCCESS) {
printf("Lỗi tạo giao thức truyền tải\n");
return 1;
}
// Bắt đầu hoạt động
status = pjsua_start();
if (status != PJ_SUCCESS) {
printf("Lỗi khởi động PJSIP\n");
return 1;
}
// Thêm tài khoản SIP
pjsua_acc_config acc_config;
pjsua_acc_config_default(&acc_config);
char uri[256];
sprintf(uri, "sip:%s@%s", USER, DOMAIN);
acc_config.id = pj_str(uri);
acc_config.reg_uri = pj_str(uri);
acc_config.cred_count = 1;
acc_config.cred_info[0].realm = pj_str("*");
acc_config.cred_info[0].scheme = pj_str("digest");
acc_config.cred_info[0].username = pj_str(USER);
acc_config.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
acc_config.cred_info[0].data = pj_str(PASS);
status = pjsua_acc_add(&acc_config, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) {
printf("Lỗi thêm tài khoản\n");
return 1;
}
printf("PJSIP đã khởi động thành công\nNhấn Enter để thoát...\n");
getchar();
// Giải phóng tài nguyên
pjsua_destroy();
return 0;
}
5. Biên dịch và chạy chương trình
Lệnh biên dịch (Linux):
bash
gcc -o pjsip_app pjsip_app.c $(pkg-config --cflags --libs libpjproject)
6. Các chức năng thường dùng
Gửi cuộc gọi đi
c
pjsua_call_id call_id;
char destination[256] = "sip:1002@sip.example.com";
pj_str_t uri = pj_str(destination);
pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, &call_id);
Kết thúc cuộc gọi
c
pjsua_call_hangup(call_id, 0, NULL, NULL);
Gửi tín hiệu DTMF
Dùng phương thức dialDtmf() để gửi chuỗi số (tự động chọn phương thức truyền tối ưu).
c
pjsua_call_dial_dtmf(call_id, "1234");
Nếu gặp lỗi PJ_EINVALIDOP, hãy đảm bảo cuộc gọi đã được thiết lập và kênh truyền media đã sẵn sàng.
Để xử lý tín hiệu DTMF nhận được, cần đăng ký hàm callback:
static void dtmf_received(pjsua_call_id call_id, int digit) {
PJ_LOG(3, (__FILE__, "Đã nhận DTMF: %c", digit));
}
// Cấu hình callback
pjsua_callback callbacks;
callbacks.on_dtmf_digit = &dtmf_received;
pjsua_set_callback(&callbacks);
7. Gỡ lỗi và ghi nhật ký
PJSIP hỗ trợ hệ thống nhật ký chi tiết. Bạn có thể điều chỉnh mức độ chi tiết như sau:
c
pjsua_logging_config log_config;
pjsua_logging_config_default(&log_config);
log_config.console_level = 4; // 1-6, giá trị càng lớn thì chi tiết càng nhiều
pjsua_init(&cfg, &log_config, NULL);
8. Học tập nâng cao
- Xử lý đa phương tiện: Hiểu cách cấu hình codec âm thanh/video
- Vượt tường lửa (NAT traversal): Cấu hình STUN, TURN và ICE
- Đa luồng: Mô hình luồng của PJSIP và cách sử dụng
- Hệ thống sự kiện: Tìm hiểu sâu hơn về hệ thống sự kiện trong PJSIP
9. Tài nguyên hữu ích
- Tài liệu chính thức: PJSIP and PJMedia Documentation | PJSIP.ORG
- Mã ví dụ:
pjproject/pjsip-apps/src/samples/