pythonmultithreadingpjsip

how can I run pjsua in a worker thread?


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?


Solution

  • 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)