I cross compiled the simple_pjsua program from the pjsip samples to use the lichee zero board and it works without any problems:
#include <stdio.h>
#include <pjsua-lib/pjsua.h>
#include <pjlib.h>
#include <pjmedia.h>
#include <pjmedia-codec.h>
#define THIS_FILE "APP"
#define SIP_DOMAIN "192.168.111.52"
#define SIP_USER "1018"
#define SIP_PASSWD "pass1018"
/* 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_status_t status;
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 */
void error_exit(const char *title, pj_status_t status)
{
pjsua_perror(THIS_FILE, title, status);
pjsua_destroy();
exit(1);
}
/*
* main()
*
* argv[1] may contain URL to call.
*/
int main()
{
pjsua_acc_id acc_id;
pj_status_t status;
/* Create pjsua first! */
status = pjsua_create();
if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
/* Init 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);
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
}
/* Add UDP transport. */
{
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);
}
/* Initialization is done, now start pjsua */
status = pjsua_start();
if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);
/* Register to SIP server by creating SIP account. */
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN);
cfg.reg_uri = pj_str("sip:" SIP_DOMAIN);
cfg.cred_count = 1;
cfg.cred_info[0].realm = pj_str("*");
cfg.cred_info[0].scheme = pj_str("digest");
cfg.cred_info[0].username = pj_str(SIP_USER);
cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg.cred_info[0].data = pj_str(SIP_PASSWD);
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding account", status);
}
/* Wait until user press "q" to quit. */
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;
}
I converted it to a library using the Makefile below:
PJDIR = ~/pjsip/pjproject-2.13
include $(PJDIR)/build.mak
simple_pjsua: simple_pjsua.o
$(PJ_CC) -shared -Wl,-soname -o libsimple_pjsua.so simple_pjsua.o $< $(PJ_LDFLAGS) $(PJ_LDLIBS)
simple_pjsua.o: simple_pjsua.c
$(PJ_CC) -c -fPIC -o $@ $< $(PJ_CFLAGS)
clean:
rm -f libsimple_pjsua.so simple_pjsua.o
Then I converted the simple_pjsua program to Python with ctype:
simple_pjsua.py:
import ctypes
import threading
import asyncio
# Load the shared library
lib = ctypes.CDLL('./libsimple_pjsua.so')
# Callback function types
CALLBACK_FUNC_TYPE = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)
MEDIA_CALLBACK_FUNC_TYPE = ctypes.CFUNCTYPE(None, ctypes.c_int)
# Callback functions
def on_incoming_call(acc_id, call_id, rdata):
print(f"Incoming call: Account ID={acc_id}, Call ID={call_id}")
def on_call_state(call_id, e):
print(f"Call {call_id} state changed")
def on_call_media_state(call_id):
print(f"Call {call_id} media state changed")
# Convert Python callbacks to C function pointers
incoming_call_cb = CALLBACK_FUNC_TYPE(on_incoming_call)
call_state_cb = CALLBACK_FUNC_TYPE(on_call_state)
call_media_state_cb = MEDIA_CALLBACK_FUNC_TYPE(on_call_media_state)
# Define C structures
class pjsip_rx_data(ctypes.Structure):
pass
class pjsua_call_info(ctypes.Structure):
pass
# Set the callback functions in C
lib.on_incoming_call = incoming_call_cb
lib.on_call_state = call_state_cb
lib.on_call_media_state = call_media_state_cb
# Initialize pjsua
lib.pjsua_create.restype = ctypes.c_int
status = lib.pjsua_create()
if status != 0:
print("Error in pjsua_create()")
exit(1)
#I use this command when I want to run simple_pjsua.py alone.
#lib.main()
#I use this command when I want to run the program in a separate thread in the main project.I will explain about it further.
def run_pjsua():
lib.main()
I run the above program with Python and it works without any problems. My problem starts when I have a PyQt5 program and when I want to run the simple_pjsua.py program using a thread in this program:
import sys
import os
import json
import threading
import asyncio
from time import sleep
from buttons import timer_callback
from udpserverasync import start_udp_listener
from PyQt5 import QtWidgets, uic
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QFontDatabase, QFont
# from simple_pjsua import pjsua_thread
import simple_pjsua
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('/root/maindesign.ui', self)
self.timer=QTimer(self)
self.timer.timeout.connect(self.animate)
self.timer.start(3000)
self.show()
def start(self):
# start the UDP listener in a separate thread
udp_listener_thread = threading.Thread(target=start_udp_listener, daemon=True)
udp_listener_thread.start()
//PJSUA Thread
pjsua_listener_thread = threading.Thread(target=simple_pjsua.run_pjsua, daemon=True)
pjsua_listener_thread.start()
.....
def animate(self):
.....
....
.....
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
fontdir= os.environ.get('QT_QPA_FONTDIR')
QFontDatabase.addApplicationFont(f'{fontdir}/TAHOMA_0.TTF')
window = Ui()
window.start()
window.show()
sys.exit(app.exec_())
Running the program gives the following error:
python3: ../src/pj/os_core_unix.c:692: pj_thread_this: Assertion `!"Calling pjlib from unknown/external thread. You must " "register external threads with pj_thread_register() " "before calling any pjlib functions."' failed.
My question is how can I run simple_pjsua.py in a worker thread?
Thanks to nanangizz
pjsua_create was incorrectly called twice, once in pjsua_simple.py and once inside run_pjsua_main(), so I commented out this part of the code in simple_pjsua.py and the project worked:
# Initialize pjsua
# lib.pjsua_create.restype = ctypes.c_int
# status = lib.pjsua_create()
# if status != 0:
# print("Error in pjsua_create()")
# exit(1)