Linux下编译pjproject-2.6并运行例程simple_pjsua

本文详细介绍使用PJSIP在Linux环境下搭建音视频通话系统的全过程,包括编译配置、常见错误排查及解决方法,并通过具体实例展示如何实现点对点通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近期项目中使用了sip协议进行音视频通话,百度的介绍是:
PJSIP同时支持语音、视频、状态呈现和即时通讯。PJSIP具有非常完善的文档,对开发者非常友好。
因特网电话(IP电话)正在向一种正式的商业电话模式演进,
SIP就是用来确保这种演进实现而需要的NGN(下一代网络)系列协议中重要的一员。支持H.264协议。
官方例程还是非常不错的,但是网上讲到使用官方例程的资料相对来讲还是比较少,并且对新手不太友好;
因为我是从头开始使用,就记录一下在Linux下从头开始编译到使用官方例程的过程;

#官方下载
http://www.pjsip.org/download.htm
我是使用了最新的pjproject-2.6版本进行编译;
如果需要早期的版本,可以通过svn直接check出来
http://svn.pjsip.org/repos/pjproject
这里写图片描述

#编译工程
这个版本是自带有Makefile文件并且使用configure不会自定生成Makefile
所以在下载后使用的时候还是有点惯性的删掉了Makefile想configure重新生成的时候失败了,其实通过configure脚本可以发现,其实2.6版本的configure是通过生成build.mak和config.status来进行相关的编译属性以及交叉编译配置的;

首先是根目录下:$ ./configure & make 并没能成功;
出现错误:

../../yuv/source/row_common.cc: In function ‘void libyuv::YuvPixel(uint8, uint8, uint8, uint8*, uint8*, uint8*, 
const libyuv::YuvConstants*)’:
../../yuv/source/row_common.cc:1256: error: invalid types ‘const signed char __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1257: error: invalid types ‘const signed char __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1258: error: invalid types ‘const signed char __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1259: error: invalid types ‘const signed char __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1260: error: invalid types ‘const short int __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1261: error: invalid types ‘const short int __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1262: error: invalid types ‘const short int __vector__[int]’ for array subscript
../../yuv/source/row_common.cc:1263: error: invalid types ‘const short int __vector__[int]’ for array subscript
make[3]: *** [output/libyuv-x86_64-unknown-linux-gnu/row_common.o] Error 1
make[3]: Leaving directory `/home/zzy/pjproject-2.6/pjproject-2.6/third_party/build/yuv'
make[2]: *** [libyuv-x86_64-unknown-linux-gnu.a] Error 2
make[2]: Leaving directory `/home/zzy/pjproject-2.6/pjproject-2.6/third_party/build/yuv'
make[1]: *** [all] Error 1
make[1]: Leaving directory `/home/zzy/pjproject-2.6/pjproject-2.6/third_party/build'
make: *** [all] Error 1
/pjproject-2.6/third_party/lib/libwebrtc-x86_64-unknown-linux-gnu.a(cpu_features.o):(.eh_frame+0x12): 
undefined reference to `__gxx_personality_v0'
collect2: ld returned 1 exit status
make[2]: *** [../bin/pjmedia-test-x86_64-unknown-linux-gnu] Error 1

这个报的是libyuv和libwebrtc库的错误,所以直接去掉相关的库的配置
$ ./configure --disable-libyuv --disable-libwebrtc & make

编译通过;

编译库的过程也是充满艰辛,网上搜索出来的结果基本上是清一色的:

# ./configure
# make dep

还在得自己慢慢看 ./configure -help
找到
–disable-libyuv
–disable-libwebrtc
libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库;
YUV主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。
与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)

WebRTC(Web Real-Time Communication)项目的最终目的主要是
让Web开发者能够基于浏览器(Chrome\FireFox…)轻易快捷开发出丰富的实时多媒体应用

接着就把生成的库以及头文件进行安装方便拷贝出来使用:

$ ./configure  --disable-libyuv --disable-libwebrtc --prefix=$PWD/_install
$ make & make install
~/_install$ ls
include  lib
~/_install/lib$ ls
libg7221codec-x86_64-unknown-linux-gnu.a        
libpjmedia-codec-x86_64-unknown-linux-gnu.a    
libpjsip-ua-x86_64-unknown-linux-gnu.a 
libresample-x86_64-unknown-linux-gnu.a
libgsmcodec-x86_64-unknown-linux-gnu.a         
libpjmedia-videodev-x86_64-unknown-linux-gnu.a  
libpjsip-x86_64-unknown-linux-gnu.a     
libspeex-x86_64-unknown-linux-gnu.a
libilbccodec-x86_64-unknown-linux-gnu.a        
libpjmedia-x86_64-unknown-linux-gnu.a          
libpjsua2-x86_64-unknown-linux-gnu.a   
libsrtp-x86_64-unknown-linux-gnu.a
libpjlib-util-x86_64-unknown-linux-gnu.a        
libpjnath-x86_64-unknown-linux-gnu.a           
ibpjsua-x86_64-unknown-linux-gnu.a     
pkgconfig
libpjmedia-audiodev-x86_64-unknown-linux-gnu.a  
libpjsip-simple-x86_64-unknown-linux-gnu.a      
libpj-x86_64-unknown-linux-gnu.a

include下面就比较多文件了,有13个文件夹…

交叉编译:
./configure --disable-libyuv --disable-libwebrtc --prefix=$PWD/_install --host=arm-none-linux-gnueabi

#工程目录介绍
pjproject-2.6的工程目录:
主要介绍:
pjlib,pjlib-util,pjmedia,pjnath,pjsip,pjsip-apps 这几个目录结构一致整齐如下
|----bin
|----build
|----docs
|----include
|----lib
|----src
build目录下的Makefile文件,可以把在src下面的所有的c/cpp文件编译成可执行的二进制文件,输出到bin目录下;
开发者可以直接在bin目录下运行各个可执行文件查看到相关接口的使用;

#测试官方Linux例程
这里使用的是pjsip-apps目录的官方例程了解一下2个设备之间的点对点通信:
进入build目录下$ make
则会把 pjsip-apps/pjsip-apps/src 的源代码编译出来,这里主要是pjsip-apps/pjsip-apps/src/samples/
这里下面的所有c/cpp文件将会被编译到pjsip-apps/bin/samples/x86_64-unknown-linux-gnu下面

调试simple_pjsua(源代码simple_pjsua.c)

这里需要有两台Ubuntu设备,开发板或者两台虚拟机 分别运行程序 ;

Ubuntu1(192.168.1.2) $ ./simple_pjsua
Ubuntu2(192.168.1.3) $ ./simple_pjsua sip:alice@192.168.1.2:5060
这里的Ubuntu2(192.168.1.3)运行程序带有参数,表示呼叫处于Ubuntu1(192.168.1.2)进程中的用户alice,当然这里同一个程序所以两个系统上都是使用了同一个用户名alice;

可以看到Ubuntu1(192.168.1.2)启动后,等待Ubuntu2(192.168.1.3)的呼入;
在Ubuntu1(192.168.1.2)可以看到来自Ubuntu2(192.168.1.3)的呼叫以及回复200

17:51:58.950            APP  ..Incoming call from <sip:alice@example.com>!!
17:51:58.958            APP  .........Call 0 state=CONNECTING
17:51:58.968            APP  ...Call 0 state=CONFIRMED
17:51:58.968   pjsua_core.c  .RX 373 bytes Request msg BYE/cseq=13927 (rdata0x7f6790002858) from UDP 192.168.1.3:5060:

在Ubuntu2(192.168.1.3)可以看到来自Ubuntu1(192.168.1.2)的回复

17:51:29.411            APP  .......Call 0 state=CALLING
17:51:29.429            APP  ......Call 0 state=CONNECTING
17:51:29.431            APP  ......Call 0 state=CONFIRMED
17:51:29.432   pjsua_core.c  .........TX 373 bytes Request msg BYE/cseq=13927 (tdta0x27ace80) to UDP 192.168.1.2:5060:

在Ubuntu2(192.168.1.3)上输入q主动挂断

Press 'h' to hangup all calls, 'q' to quit
q
18:22:17.824   pjsua_core.c !Shutting down, flags=0...
18:22:17.825   pjsua_core.c  PJSUA state changed: RUNNING --> CLOSING
18:22:17.835   pjsua_call.c !.Hangup all calls..
18:22:17.836   pjsua_call.c  ..Call 0 hanging up: code=0..
18:22:17.839   pjsua_core.c  ......TX 372 bytes Request msg BYE/cseq=9320 (tdta0x8fcc10) to UDP 192.168.1.2:5060:
BYE sip:alice@192.168.1.2:5060;ob SIP/2.0
Via: SIP/2.0/UDP 192.168.1.3:5060;rport;branch=z9hG4bKPjD.uTS.HSdsoXMVbObmK052zwAVp4k-ti
Max-Forwards: 70
From: sip:alice@example.com;tag=vWTPJAwlV8.fvlOQXE8wvGVqiTyu9PWO
To: sip:alice@192.168.1.2;tag=ZpoAP-tusbemk5oGnF-bsHEQc1pGtw3A
Call-ID: ZgBtHs1h0wqWRCADyBlkIRupOlA-6spK
CSeq: 9320 BYE
Content-Length:  0

在Ubuntu1(192.168.1.2)看到通话被Ubuntu2(192.168.1.3)结束

--end msg--
18:22:47.046   pjsua_core.c  .RX 372 bytes Request msg BYE/cseq=9320 (rdata0x7f0d40002858) from UDP 192.168.1.3:5060:
BYE sip:alice@192.168.1.2:5060;ob SIP/2.0
Via: SIP/2.0/UDP 192.168.1.3:5060;rport;branch=z9hG4bKPjD.uTS.HSdsoXMVbObmK052zwAVp4k-ti
Max-Forwards: 70
From: sip:alice@example.com;tag=vWTPJAwlV8.fvlOQXE8wvGVqiTyu9PWO
To: sip:alice@192.168.1.2;tag=ZpoAP-tusbemk5oGnF-bsHEQc1pGtw3A
Call-ID: ZgBtHs1h0wqWRCADyBlkIRupOlA-6spK
CSeq: 9320 BYE
Content-Length:  0

例程中使用的主要的两个接口
pjsua_acc_add : 传入本机URL(sip:alice@example.com)进行注册
pjsua_call_make_call : 传入呼叫方的URL(sip:alice@192.168.1.2:5060)进行呼叫
附官方例程说明:

/**
 * simple_pjsua.c
 *
 * 这是一个非常简单但功能齐全的SIP用户代理,
 * 具有以下功能
 *  - SIP 注册
 *  - 发起一个呼叫和接听一个呼叫
 *  - 音频媒体输出到声音设备上.
 *
 * 使用:
 *  - 发起一个新呼叫,启动应用程序simple_pjsua,传入参数 对方的URL进行连接
 *    例如:
 *	 ./simple_pjsua sip:user@remote
 *
 *  - 接收到呼入将自动回复200表示收到呼叫
 *
 * 本程序在完成一次简单的呼叫后,就马上退出
 */

#include <pjsua-lib/pjsua.h>

#define THIS_FILE	"APP"

#define SIP_DOMAIN	"example.com"
#define SIP_USER	"alice"
#define SIP_PASSWD	"secret"


/*  接收到新的呼入时,由底层库回调到上层 */
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));

    /* 自动回复呼入(200/OK) */
    pjsua_call_answer(call_id, 200, NULL, NULL);
}

/*  由底层库回调到上层:底层已经改变了呼叫状态(接听,挂断,...) */
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));
}

/* 由底层库回调到上层: 媒体(音视频)状态改变 */
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) {
	// 媒体连接启动时,连接到声音设备.
	pjsua_conf_connect(ci.conf_slot, 0);
	pjsua_conf_connect(0, ci.conf_slot);
    }
}

/* 输出错误信息, 并退出应用程序 */
static void error_exit(const char *title, pj_status_t status)
{
    pjsua_perror(THIS_FILE, title, status);
    pjsua_destroy();
    exit(1);
}

/*
 * main()
 *
 * argv[1] 作为呼出的参数URL
 */
int main(int argc, char *argv[])
{
    pjsua_acc_id acc_id;
    pj_status_t status;

    /* 首先创建一个PJSUA ! */
    status = pjsua_create();
    if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);

    /* 如果带有参数(URL)用于呼出的,检测呼入的URL是否有效 */
    if (argc > 1) {
	status = pjsua_verify_url(argv[1]);
	if (status != PJ_SUCCESS) error_exit("Invalid URL in argv", status);
    }

    /* 初始化 PJSUA */
    {
	pjsua_config cfg;
	pjsua_logging_config log_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;

	pjsua_logging_config_default(&log_cfg);
	log_cfg.console_level = 4;

	status = pjsua_init(&cfg, &log_cfg, NULL);
	if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
    }

    /* 加入 UDP 传输 */
    {
	pjsua_transport_config cfg;

	pjsua_transport_config_default(&cfg);
	cfg.port = 5060;
	status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
	if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
    }

    /* 初始化工作完成后,启动 PJSUA */
    status = pjsua_start();
    if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);

    /* 创建SIP账号,并注册到SIP服务器上 */
    {
	pjsua_acc_config cfg;

	pjsua_acc_config_default(&cfg);
	cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN); // sip:alice@example.com
	cfg.reg_uri = pj_str("sip:" SIP_DOMAIN); // sip:example.com
	cfg.cred_count = 1;
	cfg.cred_info[0].realm = pj_str(SIP_DOMAIN);//example.com
	cfg.cred_info[0].scheme = pj_str("digest");
	cfg.cred_info[0].username = pj_str(SIP_USER); //alice
	cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
	cfg.cred_info[0].data = pj_str(SIP_PASSWD); //secret

	status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
	if (status != PJ_SUCCESS) {
		error_exit("Error adding account", status);
		PJ_LOG(3,(THIS_FILE, "Error adding account %s",  cfg.reg_uri));
	}
    }

    /* 如果有指定的要主动呼出URL,则使用URL创建一个呼叫 */
    if (argc > 1) {
	pj_str_t uri = pj_str(argv[1]);
	status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL);
	if (status != PJ_SUCCESS){ 
		error_exit("Error making call", status); // 呼出失败
	} else {
		PJ_LOG(3,(THIS_FILE, "Success making call %s",  argv[1])); //呼出成功
	}
    }

    /* 循环等待,直到用户按下 "q" 键,则退出程序 */
    for (;;) {
	char option[10];

	puts("Press 'h' to hangup all calls, 'q' to quit");
	if (fgets(option, sizeof(option), stdin) == NULL) {
	    puts("EOF while reading stdin, will quit now..");
	    break;
	}

	if (option[0] == 'q')
	    break;

	if (option[0] == 'h')
	    pjsua_call_hangup_all();
    }

    /* Destroy pjsua */
    pjsua_destroy();

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值