diff --git a/CMakeLists.txt b/CMakeLists.txt index 938ce28..5a1d038 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.3) -project(phone VERSION 0.6.1 LANGUAGES C CXX) +project(phone VERSION 0.7.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) diff --git a/README.md b/README.md index b2e48eb..f032e47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # libphone -libphone is a library that sits on top of [PJSIP project](https://github.com/pjsip/pjproject) and tries to make it very simple to build a softphone. libphone provides a C++ and a C-API. Included with the library is a Python script that demonstrates the API [python_cli.py](src/python_cli/python_cli.py). You can download one of the [binaries](#binaries) and just enter your SIP credentials if you want to test things out. +libphone is a library that sits on top of [PJSIP project](https://github.com/pjsip/pjproject) and tries to make it very simple to build a softphone. libphone provides a C++ and a C-API. Included with the library is a Python script that demonstrates the API [python_cli.py](src/python_cli/python_cli.py). You can download one of the [binaries](#binaries) and just enter your SIP credentials if you want to test things out. Included with the library you can also find usage example in C, C++ and Java using the C-API via JNA. + ## Overview @@ -14,11 +15,14 @@ libphone is a library that sits on top of [PJSIP project](https://github.com/pjs * [1und1 configuration](#1und1-configuration) * [Handling of audio devices](#handling-of-audio-devices) * [Call-Info answer-after](#call-info-answer-after) + * [DTMF](#dtmf) + * [Handle IP address change](#handle-ip-address-change) * [Binaries](#binaries) * [Build instructions for Linux](#build-instructions-for-linux) * [Build instructions for macOS](#build-instructions-for-macos) + ## Usage You can create a phone with @@ -27,7 +31,14 @@ You can create a phone with phone = phone_create("☎️", ["194.25.0.69","194.25.0.79"], "stun.t-online.de") ``` -and connect the phone to your server via: +Starting with libphone-0.7.0 there's an additional API `phone_create_with_system_nameserver` which gives you three +possibilities to configure the system: + +1. use getaddr resolving of hostnames. If you want the behavior give the system an empty array of nameservers. +2. use the nameservers of the system for SRV lookups. +3. provide the nameservers for SRV lookups. + +Once created connect the phone to your server via: ```python phone_connect(phone, "tel.t-online.de", "your_sip_username", "your_sip_password") @@ -48,6 +59,8 @@ phone_register_on_call_state_callback(phone, on_call_state, None) The third parameter to the callback functions is required if you want to use the C-API with an object-oriented programming language like Swift: +Since 0.6.0 there is a callback for the registration state and since 0.7.0 you can register your own log function. + ```swift phone_register_on_incoming_call_callback(phone, { callId, ctx in guard let ctx else { return } @@ -177,6 +190,14 @@ if answer_after >= 0: print(f"will auto answer call after {answer_after} seconds") ``` +## DTMF +Since 0.7.0 you can send (and play) DTMF digits in a call. The sending is done via RFC2833. Sound is played locally to the user to provide auditory feedback. + + +## Handle IP address change +Since 0.7.0 there is an API call `phone_handle_ip_change`. + + ## Binaries I maintain binaries for macOS (signed, notarized and ready to go) and Ubuntu. You can get them from the GitHub release page, or my website [libphone](https://oliver-epper.de/apps/libphone/). @@ -259,8 +280,15 @@ cmake --build build-libphone --config Release --target install ## Build instructions for macOS -You can use my already prebuild package `pjproject-apple-platforms`. Install it with the following commands: +Begining with libphone-0.7.0 there is a shell-script `build-darwin-dependencies.sh` which should build all required dependencies (opus, sdl, pjproject) for: + +- iOS +- iOS simulator running on arm64 and x86_64 +- catalyst running on arm64 and x86_64 +- macOS running on arm64 and x86_64 +- macOS universal +For a quick test the old way does still work: ```shell brew tap oliverepper/made brew install pjproject-apple-platforms diff --git a/scripts/build-pjproject-catalyst-arm64.sh b/scripts/build-pjproject-catalyst-arm64.sh index c065f26..d3df1bb 100644 --- a/scripts/build-pjproject-catalyst-arm64.sh +++ b/scripts/build-pjproject-catalyst-arm64.sh @@ -1,7 +1,7 @@ #!/bin/bash # Oliver Epper -source "$(dirname $0)"/build-pjproject-darwin-base.sh +source "$(dirname "$0")"/build-pjproject-darwin-base.sh PREFIX="${PREFIX}/catalyst-arm64" rm -rf "${PREFIX}" diff --git a/src/c_cli/main.c b/src/c_cli/main.c index 117d713..446331e 100644 --- a/src/c_cli/main.c +++ b/src/c_cli/main.c @@ -79,20 +79,24 @@ void on_call_state_with_id_cb(const char* call_id, int state, void *ctx) { printf("Call %s – state: %s\n", call_id, buffer); } +void log_function(int level, const char *message, long thread_id, const char *thread_name) { + fprintf(stdout, "%s", message); +} + int main() { struct app_state *state = malloc(sizeof(struct app_state)); state->last_call_index = -1; memset(state->last_call_id, 0, sizeof(state->last_call_id)); // create phone in app state - const char *nameserver[] = {"217.237.148.22", "217.237.150.51"}; const char *stunserver[] = {"stun.t-online.de"}; - state->phone = phone_create("Cli Phone in C", nameserver, 2, stunserver, 1); + state->phone = phone_create_with_system_nameserver("Cli Phone in C", stunserver, 1); if (!state->phone) die(state->phone); // logging phone_set_log_level(0); + phone_set_log_function(state->phone, log_function); // callbacks phone_register_on_registration_state_callback(state->phone, on_registration_state, NULL); @@ -235,6 +239,29 @@ int main() { fprintf(stderr, "%s\n", phone_last_error()); } break; + case 'p': + clear_input_buffer(); + { + char dtmf_chars[128]; + printf("please dtmf characters: "); + if (read_string(dtmf_chars, sizeof(dtmf_chars)) != 0) break; + if (phone_play_dtmf_call_id(state->phone, state->last_call_id, dtmf_chars) != PHONE_STATUS_SUCCESS) + fprintf(stderr, "%s\n", phone_last_error()); + } + break; + case 'b': + phone_play_call_waiting(state->phone); + break; + case 'B': + phone_stop_call_waiting(state->phone); + break; + case '#': + printf("call count: %d\n", phone_get_call_count(state->phone)); + break; + case 'i': + printf("handle ip change\n"); + phone_handle_ip_change(); + break; default: clear_input_buffer(); break; diff --git a/src/cpp_cli/main.cpp b/src/cpp_cli/main.cpp index 705310f..b210b32 100644 --- a/src/cpp_cli/main.cpp +++ b/src/cpp_cli/main.cpp @@ -10,7 +10,7 @@ struct app_state { phone_instance_t phone; std::string last_call_id; - int last_call_index; + int last_call_index = -1; simple_task_system task_system{&phone}; [[maybe_unused]] static void on_registration_state(bool is_registered, int registration_state) { @@ -77,21 +77,39 @@ struct app_state { } } - [[maybe_unused]] static void on_call_state_with_index_cb(int call_index, int state) { + void on_call_state_with_index_cb(int call_index, int state) { + last_call_index = call_index; std::cout << "Update for call index: " << call_index << " – state: " << phone::call_state_name(state) << std::endl; + if (phone::call_state_name(state) == "DISCONNECTED") { + last_call_index = -1; + } } - [[maybe_unused]] static void on_call_state_with_id_cb(const std::string& call_id, int state) { + void on_call_state_with_id_cb(const std::string& call_id, int state) { + last_call_id = call_id; std::cout << "Update for call id: " << call_id << " – state: " << phone::call_state_name(state) << std::endl; + if (phone::call_state_name(state) == "DISCONNECTED") { + last_call_id = ""; + } } }; auto main() -> int { try { app_state state{ - phone_instance_t{"Cli Phone in C++", {"217.237.148.22", "217.237.150.51"}, {"stun.t-online.de"}}}; + phone_instance_t{ + "Cli Phone in C++", + {"stun.t-online.de"} + }}; + + // set log level phone_instance_t::set_log_level(0); + // and log function + state.phone.set_log_function([](int level, std::string_view message, long thread_id, std::string_view thread_name){ + std::cout << message; + }); + // callbacks state.phone.register_on_registration_state_callback([](bool is_registered, int registration_state) { app_state::on_registration_state(is_registered, registration_state); @@ -102,8 +120,12 @@ auto main() -> int { state.phone.register_on_incoming_call_callback([&state](const std::string& call_id) { state.on_incoming_call_cb(call_id); }); - state.phone.register_on_call_state_callback(app_state::on_call_state_with_index_cb); - state.phone.register_on_call_state_callback(app_state::on_call_state_with_id_cb); + state.phone.register_on_call_state_callback([&state](int call_index, int call_state) { + state.on_call_state_with_index_cb(call_index, call_state); + }); + state.phone.register_on_call_state_callback([&state](const std::string& call_id, int call_state) { + state.on_call_state_with_id_cb(call_id, call_state); + }); // opus state.phone.configure_opus(); @@ -171,6 +193,24 @@ auto main() -> int { std::cout << "please enter desired playback device: "; std::cin >> playback_index; phone_instance_t::set_audio_devices(capture_index, playback_index); + } else if (command == 'p') { + std::cout << "please enter dtmf characters: "; + std::string dtmf_digits; + std::cin >> dtmf_digits; + if (state.last_call_index != -1) { + state.phone.dtmf(state.last_call_index, dtmf_digits); + } + } else if (command == 'b') { + std::cout << "playing call waiting" << std::endl; + state.phone.play_call_waiting(); + } else if (command == 'B') { + std::cout << "stop call waiting" << std::endl; + state.phone.stop_call_waiting(); + } else if (command == '#') { + std::cout << "call count: " << state.phone.get_call_count() << std::endl; + } else if (command == 'i') { + std::cout << "handle ip change" << std::endl; + phone_instance_t::handle_ip_change(); } } catch (const phone::exception& e) { std::cerr << "Error: " << e.what() << std::endl; diff --git a/src/cpp_cli/simple_task_system.h b/src/cpp_cli/simple_task_system.h index 017fd9a..6bb80e1 100644 --- a/src/cpp_cli/simple_task_system.h +++ b/src/cpp_cli/simple_task_system.h @@ -16,7 +16,7 @@ class simple_task_system { phone_instance_t * m_phone_ptr; - void run(int i) { + void run(unsigned i) { m_phone_ptr->register_thread("worker_" + std::to_string(i)); while (true) { std::function f; diff --git a/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Main.java b/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Main.java index 04d0f34..f6e468b 100644 --- a/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Main.java +++ b/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Main.java @@ -7,22 +7,23 @@ public class Main { public static void main(String[] args) { var executor = Executors.newFixedThreadPool(1); - var nameservers = new String[2]; - nameservers[0] = "217.237.148.22"; - nameservers[1] = "217.237.150.51"; var stunservers = new String[1]; stunservers[0] = "stun.t-online.de"; final Phone phone; try { - phone = new Phone("Java Cli ☕️", nameservers, stunservers); + phone = new Phone("Java Cli ☕️", stunservers); Runnable register = () -> phone.registerThread("Test"); var registration = executor.submit(register); registration.get(); + phone.setLogFunction((int level, String message, long threadId, String threadName) -> { + System.out.println(message); + }); + phone.registerOnRegistrationStateCallback((isRegistered, registrationState, ctx) -> { if (isRegistered) { System.out.println("phone is registered: " + Phone.describeStatus((registrationState))); @@ -192,6 +193,38 @@ public static void main(String[] args) { e.printStackTrace(); } } + case 'p' -> { + scanner.nextLine(); + System.out.println("Please enter call index: "); + var callIndex = scanner.nextInt(); + scanner.nextLine(); + System.out.println("Please enter desired DTMF digits: "); + var digits = scanner.nextLine(); + try { + phone.dtmf(callIndex, digits); + } catch (PhoneException e) { + e.printStackTrace(); + } + } + case 'b' -> { + System.out.println("Play call waiting"); + phone.playCallWaiting(); + } + case 'B' -> { + System.out.println("Stop call waiting"); + phone.stopCallWaiting(); + } + case '#' -> { + System.out.println("Call count: " + phone.callCount()); + } + case 'i' -> { + System.out.println("Handle IP change"); + try { + phone.handleIpChange(); + } catch (PhoneException e) { + e.printStackTrace(); + } + } default -> System.out.println(""" c - call a number C - call Time Announcement of Telekom Germany @@ -203,6 +236,11 @@ public static void main(String[] args) { l - change log level d - list audio devices D - change audio devices + p - play DTMF + b - play call waiting + B - stop call waiting + # - call count + i - handle IP change q - quit """); } diff --git a/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Phone.java b/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Phone.java index e74e12b..5cebe3b 100644 --- a/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Phone.java +++ b/src/java_cli/Cli/src/main/java/de/oliver_epper/libphone/Phone.java @@ -56,9 +56,16 @@ interface CallStateIdCallback extends Callback { void onCallStateIdCallback(String callId, int state, Pointer ctx); } + interface LogFunction extends Callback { + void logFunction(int level, String message, long threadId, String threadName); + } + // PHONE_EXPORT phone_t phone_create(const char *user_agent, const char * const nameserver[], size_t nameserver_count, const char * const stunserver[], size_t stunserver_count); Pointer phone_create(String userAgent, String[] nameServers, NativeLong nameServerLength, String[] stunServers, NativeLong stunServerLength); + // PHONE_EXPORT phone_t phone_create_with_system_nameserver(const char *user_agent, const char * const stunserver[], size_t stunserver_count); + Pointer phone_create_with_system_nameserver(String userAgent, String[] stunServers, NativeLong stunServerLength); + // PHONE_EXPORT void phone_destroy(phone_t instance); void phone_destroy(Pointer instance); @@ -102,6 +109,12 @@ interface CallStateIdCallback extends Callback { int phone_hangup_call_index(Pointer instance, int call_index); // PHONE_EXPORT phone_status_t phone_hangup_call_id(phone_t instance, const char *call_id); int phone_hangup_call_id(Pointer instance, String callId); + + // PHONE_EXPORT phone_status_t phone_play_dtmf_call_index(phone_t instance, int call_index, const char *digits); + int phone_play_dtmf_call_index(Pointer instance, int call_index, String digits); + // PHONE_EXPORT phone_status_t phone_play_dtmf_call_id(phone_t instance, const char *call_id, const char *digits); + int phone_play_dtmf_call_id(Pointer instance, String call_id, String digits); + // PHONE_EXPORT void phone_hangup_calls(phone_t instance); void phone_hangup_calls(Pointer instance); @@ -142,12 +155,27 @@ interface CallStateIdCallback extends Callback { // PHONE_EXPORT void phone_set_log_level(int level); void phone_set_log_level(int level); + // PHONE_EXPORT void phone_set_log_function(phone_t instance, void (*fn)(int level, const char *message, long thread_id, const char *thread_name)); + void phone_set_log_function(Pointer instance, LogFunction logFunction); + // PHONE_EXPORT phone_status_t phone_register_thread(phone_t instance, const char *name); int phone_register_thread(Pointer instance, String name); // PHONE_EXPORT int phone_is_thread_registered(phone_t instance); boolean phone_is_thread_registered(Pointer instance); + // PHONE_EXPORT phone_status_t phone_play_call_waiting(phone_t instance); + int phone_play_call_waiting(Pointer instance); + // PHONE_EXPORT phone_status_t phone_stop_call_waiting(phone_t instance); + int phone_stop_call_waiting(Pointer instance); + + // PHONE_EXPORT unsigned phone_get_call_count(phone_t instance); + int phone_get_call_count(Pointer instance); + + // PHONE_EXPORT phone_status_t phone_handle_ip_change(void); + int phone_handle_ip_change(); + + class AudioDeviceInfo extends Structure { public int id; public Pointer driver; @@ -204,6 +232,13 @@ public Phone(String userAgent, String[] nameservers, String[] stunservers) throw } } + public Phone(String userAgent, String[] stunservers) throws PhoneException { + this.phone = CPHONE.phone_create_with_system_nameserver(userAgent, stunservers, new NativeLong(stunservers.length)); + if(this.phone == null) { + throw new PhoneException(lastError()); + } + } + void destroy() { CPHONE.phone_destroy(phone); } @@ -226,6 +261,10 @@ void call(String number) throws PhoneException { } } + void setLogFunction(PhoneLibrary.LogFunction logFunction) { + CPHONE.phone_set_log_function(phone, logFunction); + } + void registerOnRegistrationStateCallback(PhoneLibrary.RegistrationStateCallback cb) { CPHONE.phone_register_on_registration_state_callback(phone, cb, null); } @@ -258,6 +297,18 @@ void hangup(String callId) throws PhoneException { } } + void dtmf(int callIndex, String digits) throws PhoneException { + if (CPHONE.phone_play_dtmf_call_index(phone, callIndex, digits) != PHONE_STATUS_SUCCESS) { + throw new PhoneException(lastError()); + } + } + + void dtmf(String callId, String digits) throws PhoneException { + if (CPHONE.phone_play_dtmf_call_id(phone, callId, digits) != PHONE_STATUS_SUCCESS) { + throw new PhoneException(lastError()); + } + } + void hangupCalls() { CPHONE.phone_hangup_calls(phone); } @@ -363,4 +414,26 @@ void registerThread(String name) { boolean isThreadRegistered() { return CPHONE.phone_is_thread_registered(phone); } + + void playCallWaiting() throws PhoneException { + if (CPHONE.phone_play_call_waiting(phone) != PHONE_STATUS_SUCCESS) { + throw new PhoneException(lastError()); + } + } + + void stopCallWaiting() throws PhoneException { + if (CPHONE.phone_stop_call_waiting(phone) != PHONE_STATUS_SUCCESS) { + throw new PhoneException(lastError()); + } + } + + int callCount() { + return CPHONE.phone_get_call_count(phone); + } + + void handleIpChange() throws PhoneException { + if (CPHONE.phone_handle_ip_change() != PHONE_STATUS_SUCCESS) { + throw new PhoneException(lastError()); + } + } } diff --git a/src/phone/CMakeLists.txt b/src/phone/CMakeLists.txt index cf8f79e..ce9ce46 100644 --- a/src/phone/CMakeLists.txt +++ b/src/phone/CMakeLists.txt @@ -37,13 +37,15 @@ generate_export_header(phone BASE_NAME "phone") target_compile_options(phone PUBLIC ${PJPROJECT_CFLAGS}) +find_library(RESOLV_LIBRARY resolv) + target_include_directories(phone PUBLIC include ${CMAKE_CURRENT_BINARY_DIR} PRIVATE include/phone) -target_link_libraries(phone PRIVATE ${PJPROJECT_LINK_LIBRARIES}) +target_link_libraries(phone PRIVATE ${PJPROJECT_LINK_LIBRARIES} ${RESOLV_LIBRARY}) set(phone_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/phone.h diff --git a/src/phone/include/phone.h b/src/phone/include/phone.h index bb7c577..168785e 100644 --- a/src/phone/include/phone.h +++ b/src/phone/include/phone.h @@ -33,6 +33,9 @@ typedef struct { PHONE_EXPORT phone_t phone_create(const char *user_agent, const char * const nameserver[], size_t nameserver_count, const char * const stunserver[], size_t stunserver_count); +PHONE_EXPORT phone_t phone_create_with_system_nameserver(const char *user_agent, + const char * const stunserver[], size_t stunserver_count); + PHONE_EXPORT void phone_destroy(phone_t instance); PHONE_EXPORT void phone_register_on_registration_state_callback(phone_t instance, void (*cb)(int is_registered, int registration_state, void *ctx), void *ctx); @@ -61,6 +64,9 @@ PHONE_DEPRECATED_EXPORT phone_status_t phone_hangup_call(phone_t instance, int c PHONE_EXPORT phone_status_t phone_hangup_call_index(phone_t instance, int call_index); PHONE_EXPORT phone_status_t phone_hangup_call_id(phone_t instance, const char *call_id); +PHONE_EXPORT phone_status_t phone_play_dtmf_call_index(phone_t instance, int call_index, const char *digits); +PHONE_EXPORT phone_status_t phone_play_dtmf_call_id(phone_t instance, const char *call_id, const char *digits); + PHONE_EXPORT void phone_hangup_calls(phone_t instance); PHONE_EXPORT phone_status_t phone_get_call_id(phone_t instance, int call_index, char *call_id, size_t size); @@ -139,10 +145,18 @@ PHONE_EXPORT void phone_call_state_name(char *out, size_t buffer_size, int state */ PHONE_DEPRECATED_EXPORT void phone_state_name(char *out, size_t buffer_size, int state); PHONE_EXPORT void phone_set_log_level(int level); +PHONE_EXPORT void phone_set_log_function(phone_t instance, void (*fn)(int level, const char *message, long thread_id, const char *thread_name)); PHONE_EXPORT phone_status_t phone_register_thread(phone_t instance, const char *name); PHONE_EXPORT int phone_is_thread_registered(phone_t instance); +PHONE_EXPORT phone_status_t phone_play_call_waiting(phone_t instance); +PHONE_EXPORT phone_status_t phone_stop_call_waiting(phone_t instance); + +PHONE_EXPORT unsigned phone_get_call_count(phone_t instance); + +PHONE_EXPORT phone_status_t phone_handle_ip_change(void); + #ifdef __cplusplus } #endif diff --git a/src/phone/include/phone/version.h b/src/phone/include/phone/version.h index 6b42237..41a7c4e 100644 --- a/src/phone/include/phone/version.h +++ b/src/phone/include/phone/version.h @@ -2,7 +2,7 @@ #define PHONE_VERSION_H #include -#include +#include //NOLINT deprecated-headers #ifdef __cplusplus extern "C" diff --git a/src/phone/include/phone_instance_t.h b/src/phone/include/phone_instance_t.h index 522d9bf..a6dbe32 100644 --- a/src/phone/include/phone_instance_t.h +++ b/src/phone/include/phone_instance_t.h @@ -10,9 +10,11 @@ namespace pj { class Endpoint; +class ToneGenerator; } //namespace pj class account_t; +class log_writer_t; namespace phone { struct PHONE_EXPORT exception : public std::exception { @@ -69,6 +71,9 @@ class phone_instance_t { std::vector nameserver, std::vector stunserver); + explicit PHONE_EXPORT phone_instance_t(std::string user_agent, + std::vector stunserver); + PHONE_EXPORT ~phone_instance_t(); phone_instance_t() = delete; @@ -95,30 +100,44 @@ class phone_instance_t { PHONE_EXPORT void start_ringing_call(std::string call_id); PHONE_EXPORT void hangup_call(int call_index); PHONE_EXPORT void hangup_call(std::string call_id); + PHONE_EXPORT void dtmf(int call_index, const std::string& digits) const; + PHONE_EXPORT void dtmf(std::string call_id, const std::string& digits) const; PHONE_EXPORT void hangup_calls() noexcept; - PHONE_EXPORT std::string get_call_id(int call_index); - PHONE_EXPORT int get_call_index(const std::string& call_id); + [[nodiscard]] PHONE_EXPORT std::string get_call_id(int call_index) const; + [[nodiscard]] PHONE_EXPORT int get_call_index(const std::string& call_id) const; PHONE_EXPORT static void set_log_level(int level); + PHONE_EXPORT void set_log_function(const std::function& log_function); PHONE_EXPORT static void refresh_audio_devices(); - PHONE_EXPORT static std::vector get_audio_devices(); + [[nodiscard]] PHONE_EXPORT static std::vector get_audio_devices(); PHONE_EXPORT static void set_audio_devices(int capture_index, int playback_index); - PHONE_EXPORT std::optional call_incoming_message(int call_index); - PHONE_EXPORT std::optional call_incoming_message(const std::string& call_id); - PHONE_EXPORT std::optional call_answer_after(int call_index); - PHONE_EXPORT std::optional call_answer_after(const std::string& call_id); + [[nodiscard]] PHONE_EXPORT std::optional call_incoming_message(int call_index) const; + [[nodiscard]] PHONE_EXPORT std::optional call_incoming_message(const std::string& call_id) const; + [[nodiscard]] PHONE_EXPORT std::optional call_answer_after(int call_index) const; + [[nodiscard]] PHONE_EXPORT std::optional call_answer_after(const std::string& call_id) const; PHONE_EXPORT void register_thread(const std::string& name); - PHONE_EXPORT bool is_thread_registered(); + [[nodiscard]] PHONE_EXPORT bool is_thread_registered() const; + + PHONE_EXPORT void play_call_waiting() const; + PHONE_EXPORT void stop_call_waiting() const; + + [[nodiscard]] PHONE_EXPORT int get_call_count(); + + PHONE_EXPORT static void handle_ip_change(); private: - void create_tls_transport_with_srv_lookup(); std::unique_ptr m_ep; std::unique_ptr m_account; std::optional m_server; + std::unique_ptr m_call_waiting_tone_generator; + std::unique_ptr m_dtmf_tone_generator; + // FIXME: hopefully pjsip fixes the assumption about beeing the owner of the *log_writer_t + // https://github.com/pjsip/pjproject/issues/3511 + log_writer_t *m_log_writer; }; #endif //PHONE_INSTANCE_T_H \ No newline at end of file diff --git a/src/phone/include/private/account_t.h b/src/phone/include/private/account_t.h index 6f145c5..3640965 100644 --- a/src/phone/include/private/account_t.h +++ b/src/phone/include/private/account_t.h @@ -126,6 +126,11 @@ class account_t : public pj::Account { call->hangup(prm); } + void dial_dtmf(phone::CallID auto id, const std::string& digits) { + call_t *call = find_call(id); + call->dialDtmf(digits); + } + std::optional call_incoming_message(phone::CallID auto id) { call_t *call = find_call(id); return call->incoming_message; @@ -144,6 +149,12 @@ class account_t : public pj::Account { return static_cast(*find_call(call_id)); } + int get_call_count() { + unsigned call_count = pjsua_call_get_count(); + assert(call_count == m_calls.size()); + return call_count; + } + void delete_call(int call_index) noexcept { PJ_LOG(3, (__BASE_FILE__, "Going to delete call: %d", call_index)); auto it = call_iterator(call_index); diff --git a/src/phone/include/private/filename.h b/src/phone/include/private/filename.h new file mode 100644 index 0000000..eb3fa75 --- /dev/null +++ b/src/phone/include/private/filename.h @@ -0,0 +1,15 @@ +// +// Created by Oliver Epper on 18.04.23. +// + +#ifndef PHONE_FILENAME_H +#define PHONE_FILENAME_H + +#include + +std::string filename(const std::string& filepath) { + std::filesystem::path path(filepath); + return path.filename().string(); +} + +#endif //PHONE_FILENAME_H diff --git a/src/phone/include/private/log_writer_t.h b/src/phone/include/private/log_writer_t.h new file mode 100644 index 0000000..0b47dba --- /dev/null +++ b/src/phone/include/private/log_writer_t.h @@ -0,0 +1,21 @@ +// +// Created by Oliver Epper on 18.04.23. +// + +#ifndef PHONE_LOG_WRITER_T_H +#define PHONE_LOG_WRITER_T_H + +#include + +struct log_writer_t : public pj::LogWriter { + std::function log_function = + [](int level, std::string_view msg, long thread_id, std::string_view thread_name){ + pj_log_write(level, msg.data(), msg.length()); + }; + + void write(const pj::LogEntry &entry) override { + log_function(entry.level, entry.msg, entry.threadId, entry.threadName); + } +}; + +#endif //PHONE_LOG_WRITER_T_H diff --git a/src/phone/include/private/system_nameserver.h b/src/phone/include/private/system_nameserver.h new file mode 100644 index 0000000..8c1ba42 --- /dev/null +++ b/src/phone/include/private/system_nameserver.h @@ -0,0 +1,66 @@ +// +// Created by Oliver Epper on 13.04.23. +// + +#ifndef PHONE_SYSTEM_NAMESERVER_H +#define PHONE_SYSTEM_NAMESERVER_H + +#include +#include +#include +#include + +std::vector system_nameserver() { + struct __res_state res_state = {}; + + if (res_ninit(&res_state) != 0) + throw phone::exception("could not initialize resolver state"); + + int count = res_state.nscount; + + std::vector server; + server.reserve(count); + +#ifdef __linux__ + for (int i = 0; i < count; ++i) { + char ip_cstring[INET6_ADDRSTRLEN]; + + auto ipv4 = res_state.nsaddr_list[i]; + if (ipv4.sin_family == AF_INET) { + inet_ntop(AF_INET, &ipv4.sin_addr, ip_cstring, sizeof(ip_cstring)); + server.emplace_back(ip_cstring); + } + + auto ipv6 = res_state._u._ext.nsaddrs[i]; + if (ipv6 && ipv6->sin6_family == AF_INET6) { + inet_ntop(AF_INET6, &ipv6->sin6_addr, ip_cstring, sizeof(ip_cstring)); + server.emplace_back(ip_cstring); + } + } +#else + struct sockaddr_storage addresses[count]; + res_getservers(&res_state, reinterpret_cast(addresses), count); + + for (int i = 0; i < count; ++i) { + char ip_cstring[INET6_ADDRSTRLEN]; + auto address = reinterpret_cast(&addresses[i]); + + if (address->sa_family == AF_INET) { + auto ipv4_address = reinterpret_cast(address); + inet_ntop(AF_INET, &(ipv4_address->sin_addr), ip_cstring, sizeof(ip_cstring)); + server.emplace_back(ip_cstring); + } else if (address->sa_family == AF_INET6) { + auto ipv6_address = reinterpret_cast(address); + inet_ntop(AF_INET6, &(ipv6_address->sin6_addr), ip_cstring, sizeof(ip_cstring)); + server.emplace_back(ip_cstring); + } else { + continue; + } + } +#endif + res_nclose(&res_state); + + return server; +} + +#endif //PHONE_SYSTEM_NAMESERVER_H diff --git a/src/phone/include/private/tone_generator_helper.h b/src/phone/include/private/tone_generator_helper.h new file mode 100644 index 0000000..61ec583 --- /dev/null +++ b/src/phone/include/private/tone_generator_helper.h @@ -0,0 +1,37 @@ +// +// Created by Oliver Epper on 17.04.23. +// + +#ifndef PHONE_TONE_GENERATOR_HELPER_H +#define PHONE_TONE_GENERATOR_HELPER_H + +#include + +pj::ToneDescVector call_waiting_sequence() { + pj::ToneDescVector sequence{}; + pj::ToneDesc tone; + tone.freq1 = 466; + tone.freq2 = 932; + tone.on_msec = 500; + tone.off_msec = 100; + tone.volume = 5000; + sequence.push_back(tone); + tone.off_msec = 4000; + sequence.push_back(tone); + return sequence; +} + +void play_tones(pj::ToneGenerator &generator, const std::string &digits) { + pj::ToneDigitVector tone_digits; + for (const char c : digits) { + pj::ToneDigit tone_digit; + tone_digit.digit = c; + tone_digit.on_msec = 100; + tone_digit.off_msec = 20; + tone_digit.volume = 5000; + tone_digits.push_back(tone_digit); + } + generator.playDigits(tone_digits); +} + +#endif //PHONE_TONE_GENERATOR_HELPER_H diff --git a/src/phone/phone.cpp b/src/phone/phone.cpp index 01ac371..a385082 100644 --- a/src/phone/phone.cpp +++ b/src/phone/phone.cpp @@ -24,6 +24,17 @@ phone_t phone_create(const char *user_agent, } } +phone_t +phone_create_with_system_nameserver(const char *user_agent, const char *const *stunserver, size_t stunserver_count) { + const std::vector _stunserver(stunserver, stunserver + stunserver_count); + try { + return new phone_instance_t{user_agent,_stunserver}; + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return nullptr; + } +} + void phone_register_on_registration_state_callback(phone_t instance, void (*cb)(int, int, void *), void *ctx) { instance->register_on_registration_state_callback([cb, ctx](bool is_registered, int registration_state) { cb(is_registered, registration_state, ctx); @@ -176,6 +187,26 @@ void phone_hangup_calls(phone_t instance) { instance->hangup_calls(); } +phone_status_t phone_play_dtmf_call_index(phone_t instance, int call_index, const char *digits) { + try { + instance->dtmf(call_index, digits); + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return PHONE_STATUS_FAILURE; + } + return PHONE_STATUS_SUCCESS; +} + +phone_status_t phone_play_dtmf_call_id(phone_t instance, const char *call_id, const char *digits) { + try { + instance->dtmf(call_id, digits); + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return PHONE_STATUS_FAILURE; + } + return PHONE_STATUS_SUCCESS; +} + phone_status_t phone_get_call_id(phone_t instance, int call_index, char *call_id, size_t size) { try { auto id = instance->get_call_id(call_index); @@ -371,4 +402,45 @@ int phone_is_thread_registered(phone_t instance) { return instance->is_thread_registered(); } +phone_status_t phone_play_call_waiting(phone_t instance) { + try { + instance->play_call_waiting(); + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return PHONE_STATUS_FAILURE; + } + + return PHONE_STATUS_SUCCESS; +} + +phone_status_t phone_stop_call_waiting(phone_t instance) { + try { + instance->stop_call_waiting(); + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return PHONE_STATUS_FAILURE; + } + + return PHONE_STATUS_SUCCESS; +} + +unsigned phone_get_call_count(phone_t instance) { + return instance->get_call_count(); +} +void phone_set_log_function(phone_t instance, void (*fn)(int, const char *, long, const char *)) { + instance->set_log_function([fn](int level, std::string_view message, long thread_id, std::string_view thread_name){ + fn(level, message.data(), thread_id, thread_name.data()); + }); +} + +phone_status_t phone_handle_ip_change(void) { + try { + phone_instance_t::handle_ip_change(); + } catch (const phone::exception& e) { + strncpy(global_last_error, e.what(), sizeof(global_last_error)); + return PHONE_STATUS_FAILURE; + } + + return PHONE_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/src/phone/phone_instance_t.cpp b/src/phone/phone_instance_t.cpp index 65ad416..783f171 100644 --- a/src/phone/phone_instance_t.cpp +++ b/src/phone/phone_instance_t.cpp @@ -1,27 +1,51 @@ #include "phone_instance_t.h" #include "private/account_t.h" +#include "private/system_nameserver.h" +#include "private/tone_generator_helper.h" +#include "private/log_writer_t.h" #include #include -phone_instance_t::phone_instance_t(std::string user_agent, std::vector nameserver, - std::vector stunserver) : m_ep{std::make_unique()}, - m_account{std::make_unique()} { +phone_instance_t::phone_instance_t(std::string user_agent, + std::vector nameserver, + std::vector stunserver) +: m_ep{std::make_unique()}, m_account{std::make_unique()} { + m_call_waiting_tone_generator = std::make_unique(); + m_dtmf_tone_generator = std::make_unique(); + pj::EpConfig ep_cfg{}; ep_cfg.uaConfig.userAgent = std::move(user_agent); ep_cfg.uaConfig.nameserver = std::move(nameserver); ep_cfg.uaConfig.stunServer = std::move(stunserver); + // FIXME: hopefully pjsip fixes the assumption about beeing the owner of the *log_writer_t + // https://github.com/pjsip/pjproject/issues/3511 + m_log_writer = new log_writer_t{}; + ep_cfg.logConfig.writer = m_log_writer; + ep_cfg.medConfig.ecOptions = PJMEDIA_ECHO_USE_SW_ECHO; + try { m_ep->libCreate(); m_ep->libInit(ep_cfg); m_ep->libStart(); + + m_call_waiting_tone_generator->createToneGenerator(); + m_call_waiting_tone_generator->startTransmit2(m_ep->audDevManager().getPlaybackDevMedia(), {}); + + m_dtmf_tone_generator->createToneGenerator(); + m_dtmf_tone_generator->startTransmit2(m_ep->audDevManager().getPlaybackDevMedia(), {}); } catch (const pj::Error& e) { throw phone::exception{e.info()}; } } +phone_instance_t::phone_instance_t(std::string user_agent, std::vector stunserver) +: phone_instance_t(std::move(user_agent), system_nameserver(), std::move(stunserver)) {} + phone_instance_t::~phone_instance_t() { + m_dtmf_tone_generator.reset(); + m_call_waiting_tone_generator.reset(); m_ep->libDestroy(); } @@ -57,11 +81,11 @@ void phone_instance_t::configure_opus(int channel_count, int complexity, int sam } } -void phone_instance_t::create_tls_transport_with_srv_lookup() { +void create_tls_transport_with_srv_lookup(pj::Endpoint &endpoint) { pj::TransportConfig t_cfg; t_cfg.port = 0; try { - m_ep->transportCreate(PJSIP_TRANSPORT_TLS, t_cfg); + endpoint.transportCreate(PJSIP_TRANSPORT_TLS, t_cfg); } catch (const pj::Error &e) { throw phone::exception{e.info()}; } @@ -80,7 +104,7 @@ void phone_instance_t::connect(std::string server, const std::string& user, std: acc_cfg.sipConfig.authCreds.push_back(cred_info); acc_cfg.regConfig.registrarUri = "sip:" + m_server.value() + ";transport=TLS"; - create_tls_transport_with_srv_lookup(); + create_tls_transport_with_srv_lookup(*m_ep); try { m_account->create(acc_cfg, true); @@ -151,6 +175,24 @@ void phone_instance_t::hangup_call(std::string call_id) { } } +void phone_instance_t::dtmf(int call_index, const std::string &digits) const { + try { + m_account->dial_dtmf(call_index, digits); + play_tones(*m_dtmf_tone_generator, digits); + } catch (const pj::Error& e) { + throw phone::exception{e.info()}; + } +} + +void phone_instance_t::dtmf(std::string call_id, const std::string &digits) const { + try { + m_account->dial_dtmf(std::move(call_id), digits); + play_tones(*m_dtmf_tone_generator, digits); + } catch (const pj::Error& e) { + throw phone::exception{e.info()}; + } +} + void phone_instance_t::hangup_calls() noexcept { m_account->hangup_calls(); } @@ -159,7 +201,7 @@ void phone_instance_t::set_log_level(int level) { pj_log_set_level(level); } -std::string phone_instance_t::get_call_id(int call_index) { +std::string phone_instance_t::get_call_id(int call_index) const { try { return m_account->get_call_id(call_index); } catch (const std::invalid_argument& e) { @@ -167,7 +209,7 @@ std::string phone_instance_t::get_call_id(int call_index) { } } -int phone_instance_t::get_call_index(const std::string& call_id) { +int phone_instance_t::get_call_index(const std::string& call_id) const { try { return m_account->get_call_index(call_id); } catch (const std::invalid_argument& e) { @@ -218,7 +260,7 @@ void phone_instance_t::refresh_audio_devices() { pjmedia_aud_dev_refresh(); } -std::optional phone_instance_t::call_incoming_message(int call_index) { +std::optional phone_instance_t::call_incoming_message(int call_index) const { try { return m_account->call_incoming_message(call_index); } catch (const std::invalid_argument& e) { @@ -226,7 +268,7 @@ std::optional phone_instance_t::call_incoming_message(int call_inde } } -std::optional phone_instance_t::call_incoming_message(const std::string& call_id) { +std::optional phone_instance_t::call_incoming_message(const std::string& call_id) const { try { return m_account->call_incoming_message(call_id); } catch (const std::invalid_argument& e) { @@ -234,7 +276,7 @@ std::optional phone_instance_t::call_incoming_message(const std::st } } -std::optional phone_instance_t::call_answer_after(int call_index) { +std::optional phone_instance_t::call_answer_after(int call_index) const { try { return m_account->call_answer_after(call_index); } catch (const std::invalid_argument& e) { @@ -242,7 +284,7 @@ std::optional phone_instance_t::call_answer_after(int call_index) { } } -std::optional phone_instance_t::call_answer_after(const std::string& call_id) { +std::optional phone_instance_t::call_answer_after(const std::string& call_id) const { try { return m_account->call_answer_after(call_id); } catch (const std::invalid_argument& e) { @@ -258,6 +300,44 @@ void phone_instance_t::register_thread(const std::string &name) { } } -bool phone_instance_t::is_thread_registered() { +bool phone_instance_t::is_thread_registered() const { return m_ep->libIsThreadRegistered(); } + +void phone_instance_t::play_call_waiting() const { + try { + m_call_waiting_tone_generator->play(call_waiting_sequence(), true); + } catch (const pj::Error& e) { + throw phone::exception{e.info()}; + } +} + +void phone_instance_t::stop_call_waiting() const { + try { + m_call_waiting_tone_generator->stop(); + } catch (const pj::Error& e) { + throw phone::exception{e.info()}; + } +} + +int phone_instance_t::get_call_count() { + return m_account->get_call_count(); +} + +void phone_instance_t::set_log_function(const std::function& log_function) { + m_log_writer->log_function = [log_function](int level, std::string_view message, long thread_id, std::string_view thread_name){ + log_function(level, message, thread_id, thread_name); + }; +} + +void phone_instance_t::handle_ip_change() { + pjsua_ip_change_param prm; + pjsua_ip_change_param_default(&prm); + auto status = pjsua_handle_ip_change(&prm); + if (status != PJ_SUCCESS) { + char buffer[PJ_ERR_MSG_SIZE]; + pj_strerror(status, buffer, sizeof(buffer)); + throw phone::exception{buffer}; + } +} + diff --git a/src/phone/version_c.cpp.in b/src/phone/version_c.cpp.in index d9e8b9f..24ce1df 100644 --- a/src/phone/version_c.cpp.in +++ b/src/phone/version_c.cpp.in @@ -1,4 +1,3 @@ -//NOLINTBEGIN #include #include @@ -24,5 +23,4 @@ void phone_git_hash(char *out, size_t size) { void phone_git_description(char *out, size_t size) { strncpy(out, "@phone_GIT_DESCRIPTION@", size); -} -//NOLINTEND \ No newline at end of file +} \ No newline at end of file diff --git a/src/python_cli/phone_ctypes.py b/src/python_cli/phone_ctypes.py index 26f9088..b73d21a 100644 --- a/src/python_cli/phone_ctypes.py +++ b/src/python_cli/phone_ctypes.py @@ -69,6 +69,21 @@ def phone_create(user_agent, nameservers, stunservers): return __phone_create(c_user_agent, c_nameservers, len(nameservers), c_stunservers, len(stunservers)) +# PHONE_EXPORT phone_t phone_create_with_system_nameserver(const char *user_agent, +# const char * const stunserver[], size_t stunserver_count); +def phone_create_with_system_nameserver(user_agent, stunservers): + __phone_create_with_system_nameserver = libphone.phone_create_with_system_nameserver + __phone_create_with_system_nameserver.restype = c_void_p + __phone_create_with_system_nameserver.argtypes = [c_char_p, POINTER(c_char_p), c_size_t] + c_user_agent = c_char_p(user_agent.encode('utf-8')) + + c_stunservers = (c_char_p * len(stunservers))() + for i in range(len(stunservers)): + c_stunservers[i] = c_char_p(stunservers[i].encode('utf-8')) + + return __phone_create_with_system_nameserver(c_user_agent, c_stunservers, len(stunservers)) + + # PHONE_EXPORT void phone_destroy(phone_t instance); phone_destroy = libphone.phone_destroy phone_destroy.restype = None @@ -166,6 +181,36 @@ def phone_answer_call_id(phone, call_id): __phone_hangup_call_index.argtypes = [c_void_p, c_int] +# PHONE_EXPORT phone_status_t phone_hangup_call_id(phone_t instance, const char *call_id); +def phone_hangup_call_id(phone, call_id): + __phone_hangup_call_id = libphone.phone_hangup_call_id + __phone_hangup_call_id.restype = c_int + __phone_hangup_call_id.argtypes = [c_void_p, c_char_p] + c_call_id = c_char_p(call_id.encode('utf-8')) + return __phone_hangup_call_id(phone, c_call_id) + + +# PHONE_EXPORT phone_status_t phone_play_dtmf_call_index(phone_t instance, int call_index, const char *digits); +def phone_play_dtmf_call_index(phone, call_index, digits): + __phone_play_dtmf_call_index = libphone.phone_play_dtmf_call_index + __phone_play_dtmf_call_index.restype = c_int + __phone_play_dtmf_call_index.argtypes = [c_void_p, c_int, c_char_p] + c_digits = c_char_p(digits.encode('utf-8')) + if __phone_play_dtmf_call_index(phone, call_index, c_digits) != PHONE_STATUS_SUCCESS: + raise Exception(phone_last_error()) + + +# PHONE_EXPORT phone_status_t phone_play_dtmf_call_id(phone_t instance, const char *call_id, const char *digits); +def phone_play_dtmf_call_id(phone, call_id, digits): + __phone_play_dtmf_call_id = libphone.phone_play_dtmf_call_id + __phone_play_dtmf_call_id.restype = c_int + __phone_play_dtmf_call_id.argtypes = [c_void_p, c_char_p, c_char_p] + c_call_id = c_char_p(call_id.encode('utf-8')) + c_digits = c_char_p(digits.encode('utf-8')) + if __phone_play_dtmf_call_id(phone, c_call_id, c_digits) != PHONE_STATUS_SUCCESS: + raise Exception(phone_last_error()) + + # PHONE_EXPORT phone_status_t phone_start_ringing_call_index(phone_t instance, int call_index); phone_start_ringing_call_index = libphone.phone_start_ringing_call_index phone_start_ringing_call_index.restype = c_int @@ -181,15 +226,6 @@ def phone_start_ringing_call_id(phone, call_id): return __phone_start_ringing_call_id(phone, c_call_id) -# PHONE_EXPORT phone_status_t phone_hangup_call_id(phone_t instance, const char *call_id); -def phone_hangup_call_id(phone, call_id): - __phone_hangup_call_id = libphone.phone_hangup_call_id - __phone_hangup_call_id.restype = c_int - __phone_hangup_call_id.argtypes = [c_void_p, c_char_p] - c_call_id = c_char_p(call_id.encode('utf-8')) - return __phone_hangup_call_id(phone, c_call_id) - - # PHONE_DEPRECATED_EXPORT phone_status_t phone_hangup_call(phone_t instance, int call_id); phone_hangup_call = __phone_hangup_call_index @@ -267,7 +303,7 @@ def phone_get_audio_device_names(device_filter): # PHONE_EXPORT phone_status_t phone_get_audio_devices(audio_device_info_t *devices, size_t *devices_count, size_t max_driver_name_length, size_t max_device_name_length, device_filter_t filter); -class phone_audio_device_info: +class PhoneAudioDeviceInfo: def __init__(self, id, driver, name, input_count, output_count): self.id = id self.driver = driver @@ -277,7 +313,7 @@ def __init__(self, id, driver, name, input_count, output_count): def phone_get_audio_devices(device_filter): - class c_phone_audio_device_info_t(Structure): + class CPhoneAudioDeviceInfoT(Structure): _fields_ = [ ('id', c_int), ('driver', c_char_p), @@ -287,13 +323,13 @@ class c_phone_audio_device_info_t(Structure): ] __phone_get_audio_devices = libphone.phone_get_audio_devices __phone_get_audio_devices.restype = c_int - __phone_get_audio_devices.argtypes = [POINTER(c_phone_audio_device_info_t), POINTER(c_size_t), c_size_t, c_size_t, device_filter_t] + __phone_get_audio_devices.argtypes = [POINTER(CPhoneAudioDeviceInfoT), POINTER(c_size_t), c_size_t, c_size_t, device_filter_t] if not DEVICE_FILTER_NONE <= device_filter <= DEVICE_FILTER_OUTPUT: device_filter = DEVICE_FILTER_NONE c_count = c_size_t(phone_get_audio_devices_count()) max_device_driver_name_length = phone_get_audio_device_driver_name_length() + 1 # +1 for zero termination max_device_name_length = phone_get_audio_device_info_name_length() + 1 # +1 for zero termination - devices = (c_phone_audio_device_info_t * c_count.value)() + devices = (CPhoneAudioDeviceInfoT * c_count.value)() for i in range(c_count.value): devices[i].driver = cast(create_string_buffer(max_device_driver_name_length), c_char_p) @@ -303,7 +339,7 @@ class c_phone_audio_device_info_t(Structure): raise Exception(phone_last_error()) def to_python(c_phone_audio_device_info): - return phone_audio_device_info( + return PhoneAudioDeviceInfo( c_phone_audio_device_info.id, c_phone_audio_device_info.driver.decode('utf-8'), c_phone_audio_device_info.name.decode('utf-8'), @@ -415,6 +451,35 @@ def phone_git_description(): __phone_git_description(buffer, len(buffer)) return buffer.value.decode('utf-8') +# PHONE_EXPORT void phone_set_log_function(phone_t instance, void (*fn)(int level, const char *message, long thread_id, const char *thread_name)); +phone_set_log_function = libphone.phone_set_log_function +phone_set_log_function.restype = None +phone_set_log_function.argtypes = [c_void_p, c_void_p] + + +# PHONE_EXPORT phone_status_t phone_play_call_waiting(phone_t instance); +phone_play_call_waiting = libphone.phone_play_call_waiting +phone_play_call_waiting.restype = None +phone_play_call_waiting.argtypes = [c_void_p] + + +# PHONE_EXPORT phone_status_t phone_stop_call_waiting(phone_t instance); +phone_stop_call_waiting = libphone.phone_stop_call_waiting +phone_stop_call_waiting.restype = None +phone_stop_call_waiting.argtypes = [c_void_p] + + +# PHONE_EXPORT unsigned phone_get_call_count(phone_t instance); +phone_get_call_count = libphone.phone_get_call_count +phone_get_call_count.restype = c_int +phone_get_call_count.argtypes = [c_void_p] + + +# PHONE_EXPORT phone_status_t phone_handle_ip_change(void); +phone_handle_ip_change = libphone.phone_handle_ip_change +phone_handle_ip_change.restype = None +phone_handle_ip_change.argtypes = [c_void_p] + def die(instance): phone_destroy(instance) @@ -433,5 +498,11 @@ def die(instance): l - change log level d - list audio devices D - change audio devices +p - play DTMF in call +P - play DTMF in call +b - play call waiting +B - stop call waiting +# - print call count +i - handle ip change q - quit ''' diff --git a/src/python_cli/python_cli.py b/src/python_cli/python_cli.py index 0ba809b..363dc25 100644 --- a/src/python_cli/python_cli.py +++ b/src/python_cli/python_cli.py @@ -11,7 +11,6 @@ from phone_ctypes import * useragent = "Python CLI Phone" -nameservers = ["217.237.148.22", "217.237.150.51"] stunservers = ["stun.t-online.de"] sipserver = "tel.t-online.de" username = "+49..." @@ -26,12 +25,17 @@ exit(1) -phone = phone_create(useragent, nameservers, stunservers) +phone = phone_create_with_system_nameserver(useragent, stunservers) if phone is None: die(phone) phone_set_log_level(0) +@CFUNCTYPE(None, c_int, c_char_p, c_long, c_char_p) +def log_function(level, message, thread_id, thread_name): + print(f"-> {message.decode('utf-8')}", end='') + + # callbacks @CFUNCTYPE(None, c_int, c_int, c_void_p) def on_registration_state_cb(is_registered, registration_state, ctx): @@ -82,6 +86,8 @@ def on_call_state_index_cb(call_index, state, ctx): def on_call_state_id_cb(call_id, state, ctx): print(f"Call id: {call_id.decode('utf-8')} and index: {phone_get_call_index(phone, call_id)} – state: {phone_call_state_name(state)}") + +phone_set_log_function(phone, log_function) phone_register_on_registration_state_callback(phone, on_registration_state_cb, None) phone_register_on_incoming_call_index_callback(phone, on_incoming_call_index_cb, None) phone_register_on_incoming_call_id_callback(phone, on_incoming_call_id_cb, None) @@ -154,6 +160,32 @@ def on_call_state_id_cb(call_id, state, ctx): playback_device = int(input("please enter desired playback device: ")) if phone_set_audio_devices(capture_device, playback_device) != PHONE_STATUS_SUCCESS: print(phone_last_error(), file=sys.stderr) + elif command == 'p': + call_index = int(input("please enter call index: ")) + digits = input("please enter DTMF digits: ") + try: + phone_play_dtmf_call_index(phone, call_index, digits) + except Exception as e: + print(e) + elif command == 'P': + call_id = input("Please enter call id: ") + digits = input("Please enter DTMF digits: ") + try: + phone_play_dtmf_call_id(phone, call_id, digits) + except Exception as e: + print(e) + elif command == 'b': + print("play call waiting") + phone_play_call_waiting(phone) + elif command == 'B': + print("stop call waiting") + phone_stop_call_waiting(phone) + elif command == '#': + print(f"number of calls: {phone_get_call_count(phone)}") + elif command == 'i': + print("handling ip change") + phone_handle_ip_change(phone) + print("shutting down...") phone_destroy(phone)