#include <pjsua2.hpp>
#include <pjsua-lib/pjsua.h>
#include <iostream>
#include <memory>
#include <atomic>
#include <mutex>
#include <map>
#ifdef _WIN32
#include <windows.h>
#endif
#include "pjsua2_wrapper.h"
using namespace pj;
namespace {
std::atomic<bool> g_initialized{false};
std::unique_ptr<Endpoint> g_endpoint;
std::mutex g_mutex;
std::map<int, std::shared_ptr<Account>> g_accounts;
std::map<int, std::shared_ptr<Call>> g_calls;
std::atomic<int> g_next_account_id{0};
void (*g_on_reg_state)(int acc_id, int is_registered) = nullptr;
void (*g_on_incoming_call)(int acc_id, int call_id, const char* remote_uri) = nullptr;
void (*g_on_call_state)(int call_id, int state) = nullptr;
void (*g_on_call_media_state)(int call_id, int state) = nullptr;
}
class MyAccount : public Account {
private:
int m_id;
public:
MyAccount(int id) : m_id(id) {}
virtual ~MyAccount() {}
virtual void onRegState(OnRegStateParam &prm) {
std::cout << "[PJSUA2] Account " << m_id << " registration state changed\n";
AccountInfo ai = getInfo();
bool registered = ai.regIsActive;
std::cout << "[PJSUA2] Registration status: " << (registered ? "registered" : "not registered") << "\n";
if (g_on_reg_state) {
std::cout << "[PJSUA2] Calling registration callback for account " << m_id << "\n";
g_on_reg_state(m_id, registered ? 1 : 0);
}
}
virtual void onIncomingCall(OnIncomingCallParam &iprm) {
std::cout << "[PJSUA2] Incoming call to account " << m_id << "\n";
Call *call = new Call(*this, iprm.callId);
int call_id = call->getId();
CallInfo ci = call->getInfo();
g_calls[call_id] = std::shared_ptr<Call>(call);
if (g_on_incoming_call) {
g_on_incoming_call(m_id, call_id, ci.remoteUri.c_str());
}
}
};
extern "C" {
int pjsua2_create_endpoint() {
std::lock_guard<std::mutex> lock(g_mutex);
if (g_initialized.load()) {
return 0;
}
try {
g_endpoint = std::make_unique<Endpoint>();
g_endpoint->libCreate();
g_initialized.store(true);
std::cout << "[PJSUA2] Endpoint created\n";
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_init_endpoint(int log_level) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
EpConfig cfg;
cfg.logConfig.level = log_level;
cfg.logConfig.consoleLevel = log_level;
#ifdef _WIN32
char tempPath[MAX_PATH];
if (GetTempPathA(MAX_PATH, tempPath)) {
cfg.logConfig.filename = std::string(tempPath) + "pjsua2_test.log";
}
#endif
cfg.uaConfig.userAgent = "PJSUA2-Test/1.0";
cfg.uaConfig.maxCalls = 4;
g_endpoint->libInit(cfg);
std::cout << "[PJSUA2] Endpoint initialized\n";
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_create_transport(int port) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
TransportConfig tcfg;
tcfg.port = port;
g_endpoint->transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
std::cout << "[PJSUA2] Transport created on port " << port << "\n";
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_start() {
std::lock_guard<std::mutex> lock(g_mutex);
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
g_endpoint->libStart();
std::cout << "[PJSUA2] Started\n";
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
void pjsua2_destroy() {
std::lock_guard<std::mutex> lock(g_mutex);
if (!g_initialized.load() || !g_endpoint) {
return;
}
try {
g_accounts.clear();
g_calls.clear();
g_endpoint->libDestroy();
g_endpoint.reset();
g_initialized.store(false);
std::cout << "[PJSUA2] Destroyed\n";
} catch (...) {
g_endpoint.reset();
g_initialized.store(false);
}
}
int pjsua2_create_account_simple(const char* id_uri, const char* registrar_uri,
const char* username, const char* password) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
AccountConfig cfg;
cfg.idUri = id_uri;
cfg.regConfig.registrarUri = registrar_uri;
AuthCredInfo cred("digest", "*", username, 0, password);
cfg.sipConfig.authCreds.push_back(cred);
int id = g_next_account_id.fetch_add(1);
auto acc = std::make_shared<MyAccount>(id); acc->create(cfg);
g_accounts[id] = acc;
std::cout << "[PJSUA2] Account created with ID " << id << "\n";
return id;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_create_account(const char* id_uri, const char* registrar_uri,
const char* username, const char* password,
void (*on_reg_state)(int is_registered),
void (*on_incoming_call)(int call_id, const char* remote_uri)) {
return pjsua2_create_account_simple(id_uri, registrar_uri, username, password);
}
int pjsua2_make_call(int acc_id, const char* dst_uri) {
std::lock_guard<std::mutex> lock(g_mutex);
auto it = g_accounts.find(acc_id);
if (it == g_accounts.end()) {
return -1;
}
try {
Call* call = new Call(*it->second);
CallOpParam prm;
prm.opt.audioCount = 1;
prm.opt.videoCount = 0;
call->makeCall(dst_uri, prm);
int call_id = call->getId();
g_calls[call_id] = std::shared_ptr<Call>(call);
std::cout << "[PJSUA2] Call initiated: " << call_id << "\n";
return call_id;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_answer_call(int call_id, int code) {
std::lock_guard<std::mutex> lock(g_mutex);
auto it = g_calls.find(call_id);
if (it == g_calls.end()) {
return -1;
}
try {
CallOpParam prm;
prm.statusCode = (pjsip_status_code)code;
it->second->answer(prm);
std::cout << "[PJSUA2] Call " << call_id << " answered\n";
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_hangup_call(int call_id) {
std::lock_guard<std::mutex> lock(g_mutex);
auto it = g_calls.find(call_id);
if (it == g_calls.end()) {
return -1;
}
try {
CallOpParam prm;
it->second->hangup(prm);
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
int pjsua2_get_audio_device_count() {
if (!g_initialized.load() || !g_endpoint) {
return 0;
}
try {
AudDevManager& mgr = g_endpoint->audDevManager();
const AudioDevInfoVector2 devs = mgr.enumDev2();
return static_cast<int>(devs.size());
} catch (...) {
return 0;
}
}
int pjsua2_get_audio_device_info(int index, char* name, int name_size,
int* is_capture, int* is_playback) {
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
AudDevManager& mgr = g_endpoint->audDevManager();
const AudioDevInfoVector2 devs = mgr.enumDev2();
if (index < 0 || index >= (int)devs.size()) {
return -1;
}
strncpy(name, devs[index].name.c_str(), name_size - 1);
name[name_size - 1] = '\0';
*is_capture = devs[index].inputCount > 0 ? 1 : 0;
*is_playback = devs[index].outputCount > 0 ? 1 : 0;
return devs[index].id;
} catch (...) {
return -1;
}
}
int pjsua2_set_audio_devices(int capture_dev, int playback_dev) {
if (!g_initialized.load() || !g_endpoint) {
return -1;
}
try {
AudDevManager& mgr = g_endpoint->audDevManager();
mgr.setCaptureDev(capture_dev);
mgr.setPlaybackDev(playback_dev);
return 0;
} catch (Error& err) {
std::cerr << "[PJSUA2] Error: " << err.reason << "\n";
return -1;
}
}
void pjsua2_set_callbacks(void (*on_reg_state)(int acc_id, int is_registered),
void (*on_incoming_call)(int acc_id, int call_id, const char* remote_uri),
void (*on_call_state)(int call_id, int state),
void (*on_call_media_state)(int call_id, int state)) {
std::cout << "[PJSUA2] Setting global callbacks\n";
g_on_reg_state = on_reg_state;
g_on_incoming_call = on_incoming_call;
g_on_call_state = on_call_state;
g_on_call_media_state = on_call_media_state;
}
int pjsua2_test_basic() { return 42; }
int pjsua2_test_object_creation() { return 0; }
int pjsua2_test_account_config() { return 0; }
int pjsua2_test_endpoint_state() {
if (!g_initialized.load() || !g_endpoint) return -1;
try { return g_endpoint->libGetState(); } catch (...) { return -1; }
}
int pjsua2_test_create_account() { return 0; }
}