Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverepper committed Mar 26, 2023
2 parents d714b51 + cb49384 commit fc0fa57
Show file tree
Hide file tree
Showing 16 changed files with 514 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ build
cmake-build-debug
.idea
TODO.md
cmake/credentials.cmake
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.3)

project(phone VERSION 0.3.0 LANGUAGES C CXX)
project(phone VERSION 0.5.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 20)

Expand All @@ -25,6 +25,12 @@ endif()
message(DEBUG "CMAKE_INSTALL_RPATH: ${CMAKE_INSTALL_RPATH}")
list(POP_BACK CMAKE_MESSAGE_INDENT)

if (EXISTS "${CMAKE_SOURCE_DIR}/cmake/credentials.cmake")
include(cmake/credentials.cmake)
else()
message(FATAL_ERROR "please copy cmake/credentials.cmake.example to cmake/credentials.cmake")
endif()

add_subdirectory(src bin)

install(TARGETS phone_sharedlib)
74 changes: 69 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ libphone is a library that sits on top of [PJSIP project](https://github.com/pjs
* [Overview](#overview)
* [Usage](#usage)
* [Callbacks](#callbacks)
* [Two ways to reference a call](#two-ways-to-reference-a-call)
* [CLI Demo Phone](#cli-demo-phone)
* [Compatibility with SIP providers](#compatibility-with-sip-providers)
* [1und1 configuration](#1und1-configuration)
* [Handling of audio devices](#handling-of-audio-devices)
* [Call-Info answer-after](#call-info-answer-after)
* [Binaries](#binaries)
* [Build instructions for Linux](#build-instructions-for-linux)
* [Build instructions for macOS](#build-instructions-for-macos)
Expand Down Expand Up @@ -55,6 +58,21 @@ phone_register_on_incoming_call_callback(phone, { callId, ctx in
}, Unmanaged.passRetained(self).toOpaque())
```

## Two ways to reference a call
Since version 0.2.0 libphone can reference a call via index or id. So every API call that references a call exists in two versions. One that takes an int parameter that is the call-index or a string that represents the call id. You can register callbacks that deliver one or the other to you.

```c
void phone_register_on_incoming_call_index_callback(phone_t instance, void (*cb)(int call_index, void *ctx), void *ctx);
void phone_register_on_incoming_call_id_callback(phone_t instance, void (*cb)(const char *call_id, void *ctx), void *ctx);
```
Or to hang up a call, for example:
```c
phone_status_t phone_hangup_call_index(phone_t instance, int call_index);
phone_status_t phone_hangup_call_id(phone_t instance, const char *call_id);
```

## CLI Demo Phone

![CLI Demo Phone](python_cli.png)
Expand Down Expand Up @@ -109,13 +127,59 @@ buddy = "+491804100100"

If you provide nameservers the library will use SRV lookup which is not supported by 1und1. Providing no nameservers the library will fall back to use the getaddr call to resolv just the sip-servers ip address.

## Binaries
## Handling of audio devices

You can select the desired audio capture and audio playback device at any time. To list all the devices available in your system you can call:

```c
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);
```
You can iterate over the resulting array and print out all the device information:
```python
for device in phone_get_audio_devices(device_filter):
print(f"{device.id} - {device.driver}/{device.name} ({device.input_count}/{device.output_count})")
```

![CLI Demo Phone Audio List](python_cli_list_audio_devices.png)

To select capture and playback device use the function `phone_set_audio_devices`:

If you want to try this out I maintain the following binary distributions:
```c
phone_status_t phone_set_audio_devices(int capture_device, int playback_device);
```
## Call-Info answer-after
There are certain information from the incoming SIP-INVITE that are saved in the call. The `answer-after` value is one of them. You can check if the INVITE had a header like this:
```pre
Call-Info: <sip:SERVER>;answer-after=SECONDS
```

and receive the `SECONDS` via:

```c
phone_status_t phone_call_answer_after_index(phone_t instance, int call_index, int *answer_after);
```
or
```c
phone_status_t phone_call_answer_after_id(phone_t instance, const char *call_id, int *answer_after);
```

You can wrap that in a more pleasant call for your language of choice.
Python example:

```python
answer_after = phone_call_answer_after_index(phone, call_index)
if answer_after >= 0:
print(f"will auto answer call after {answer_after} seconds")
```

## Binaries

- [libphone.0.0.1-macos-universal.zip](https://oliver-epper.de/libphone.0.0.1-macos-universal.zip) (signed and notarized, ready to go)
- [libphone.0.0.1-ubuntu22.04-aarch64.tgz](https://oliver-epper.de/libphone.0.0.1-ubuntu22.04-aarch64.tgz)
- [libphone.0.0.1-ubuntu22.04-x86_64.tgz](https://oliver-epper.de/libphone.0.0.1-ubuntu22.04-x86_64.tgz)
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/).

Depending on your type of Ubuntu installation you might need to install the following as dependencies:

Expand Down
6 changes: 6 additions & 0 deletions cmake/credentials.cmake.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
add_definitions(
-DSERVER="tel.t-online.de"
-DUSER="+4965191899543"
-DPASSWORD=NULL
-DPASSWORD_FUNCTION=std::nullopt
-DBUDDY_NUMBER="+491804100100")
Binary file added python_cli_list_audio_devices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 32 additions & 14 deletions src/c_cli/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <phone/version.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void die(phone_t instance) {
phone_destroy(instance);
Expand All @@ -21,10 +22,17 @@ void on_incoming_call_with_index_cb(int call_index, __attribute__((unused)) void
s->last_call_index = call_index;

char call_id_buffer[128] = {0};
if (phone_get_call_id(s->phone, 100, call_id_buffer, sizeof(call_id_buffer)) != PHONE_STATUS_SUCCESS)
if (phone_get_call_id(s->phone, call_index, call_id_buffer, sizeof(call_id_buffer)) != PHONE_STATUS_SUCCESS)
fprintf(stderr, "%s\n", phone_last_error());

printf("Incoming call index: %d, id: %s\n", call_index, call_id_buffer);

int answer_after;
phone_call_answer_after_index(s->phone, call_index, &answer_after);
if (answer_after >= 0) {
sleep(answer_after);
phone_answer_call_index(s->phone, call_index);
}
}

void on_incoming_call_with_id_cb(const char *call_id, __attribute__((unused)) void *ctx) {
Expand All @@ -35,6 +43,13 @@ void on_incoming_call_with_id_cb(const char *call_id, __attribute__((unused)) vo
fprintf(stderr, "%s\n", phone_last_error());

printf("Incoming call id: %s, index: %d\n", call_id, s->last_call_index);

int answer_after;
phone_call_answer_after_id(s->phone, call_id, &answer_after);
if (answer_after >= 0) {
sleep(answer_after);
phone_answer_call_id(s->phone, call_id);
}
}

void on_call_state_with_index_cb(int call_index, int state, void *ctx) {
Expand All @@ -48,10 +63,11 @@ void on_call_state_with_index_cb(int call_index, int state, void *ctx) {
}

void on_call_state_with_id_cb(const char* call_id, int state, void *ctx) {
char buffer[64];
phone_state_name(buffer, sizeof(buffer), state);
struct app_state *s = (struct app_state*)ctx;
strncpy(s->last_call_id, call_id, sizeof(s->last_call_id));

char buffer[64];
phone_state_name(buffer, sizeof(buffer), state);
printf("Call %s – state: %s\n", call_id, buffer);
}

Expand Down Expand Up @@ -82,9 +98,7 @@ int main() {
die(state->phone);

// connect
if (phone_connect(state->phone,
"tel.t-online.de",
"+4965191899543", NULL) != PHONE_STATUS_SUCCESS)
if (phone_connect(state->phone, SERVER, USER, PASSWORD) != PHONE_STATUS_SUCCESS)
die(state->phone);

// repl
Expand Down Expand Up @@ -115,7 +129,7 @@ int main() {
break;
case 'C':
clear_input_buffer();
if (phone_make_call(state->phone, "+491804100100") != PHONE_STATUS_SUCCESS)
if (phone_make_call(state->phone, BUDDY_NUMBER) != PHONE_STATUS_SUCCESS)
fprintf(stderr, "%s\n", phone_last_error());
break;
case 'a':
Expand Down Expand Up @@ -176,21 +190,25 @@ int main() {
{
phone_refresh_audio_devices();
size_t count = phone_get_audio_devices_count();
size_t max_device_name_length = phone_get_audio_device_info_name_length();
char *device_names[count];
char data[count][max_device_name_length];
size_t max_driver_name_length = phone_get_audio_device_driver_name_length() + 1; // +1 for zero termination
size_t max_device_name_length = phone_get_audio_device_info_name_length() + 1; // +1 for zero termination

audio_device_info_t devices[count];
char driver_names[count][max_driver_name_length];
char device_names[count][max_device_name_length];

int i;
for (i = 0; i < count; i++) {
device_names[i] = data[i];
memset(data[i], 0, sizeof(max_device_name_length));
devices[i].driver = driver_names[i];
devices[i].name = device_names[i];
}

if (phone_get_audio_device_names(device_names, &count, max_device_name_length, DEVICE_FILTER_NONE) != PHONE_STATUS_SUCCESS)
if (phone_get_audio_devices(devices, &count, max_driver_name_length, max_device_name_length,
DEVICE_FILTER_NONE) != PHONE_STATUS_SUCCESS)
fprintf(stderr, "%s\n", phone_last_error());

for (i = 0; i < count; i++) {
printf("%d - %s\n", i, device_names[i]);
printf("%d - %s/%s (%d/%d)\n", devices[i].id, devices[i].driver, devices[i].name, devices[i].input_count, devices[i].output_count);
}
}
break;
Expand Down
34 changes: 28 additions & 6 deletions src/cpp_cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <iostream>
#include <thread>

auto password_function = []() { return PASSWORD; };

struct app_state {
phone_instance_t phone;
std::string last_call_id;
Expand All @@ -14,14 +16,34 @@ struct app_state {
<< " – Incoming call index: " << last_call_index
<< ", call id: " << phone.get_call_id(last_call_index)
<< std::endl;
auto incoming_message = phone.call_incoming_message(call_index);
if (incoming_message.has_value())
std::cout << incoming_message.value().substr(0, 10) + "... " << std::endl;
auto answer_after = phone.call_answer_after(call_index);
if (answer_after.has_value()) {
// FIXME: push this in another thread, need phone_register_thread, first.
std::cout << "Will auto-answer call for you" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(answer_after.value()));
phone.answer_call(call_index);
}
}

void on_incoming_call_cb(std::string call_id) {
last_call_id = std::move(call_id);
void on_incoming_call_cb(const std::string& call_id) {
last_call_id = call_id;
std::cout << std::this_thread::get_id()
<< " - Incoming call id: " << last_call_id
<< ", call index: " << phone.get_call_index(last_call_id)
<< std::endl;
auto incoming_message = phone.call_incoming_message(call_id);
if (incoming_message.has_value())
std::cout << incoming_message.value().substr(0, 10) + "... " << std::endl;
auto answer_after = phone.call_answer_after(call_id);
if (answer_after.has_value()) {
// FIXME: push this in another thread, need phone_register_thread, first.
std::cout << "Will auto-answer call for you" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(answer_after.value()));
phone.answer_call(call_id);
}
}

[[maybe_unused]] static void on_call_state_with_index_cb(int call_index, int state) {
Expand All @@ -43,8 +65,8 @@ auto main() -> int {
state.phone.register_on_incoming_call_callback([&state](int call_index) {
state.on_incoming_call_cb(call_index);
});
state.phone.register_on_incoming_call_callback([&state](std::string call_id) {
state.on_incoming_call_cb(std::move(call_id));
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);
Expand All @@ -53,7 +75,7 @@ auto main() -> int {
state.phone.configure_opus();

// connect
state.phone.connect("tel.t-online.de", "+4965191899543");
state.phone.connect(SERVER, USER, PASSWORD_FUNCTION);

// repl
char command = 'q';
Expand All @@ -72,7 +94,7 @@ auto main() -> int {
std::cin >> number;
state.phone.make_call(number);
} else if (command == 'C') {
state.phone.make_call("+491804100100");
state.phone.make_call(BUDDY_NUMBER);
} else if (command == 'a') {
int call_index;
std::cout << "please enter call index: ";
Expand Down
56 changes: 50 additions & 6 deletions src/phone/include/phone.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ typedef struct phone_instance_t *phone_t;
typedef int phone_status_t;

typedef enum {
// IDEA ABI breaking change better names: DEVICE_FILTER_NO_FILTER/ALL_DEVICES, DEVICE_FILTER_INPUT_DEVICES
DEVICE_FILTER_NONE,
DEVICE_FILTER_INPUT,
DEVICE_FILTER_OUTPUT
} device_filter_t;

typedef struct {
int id;
char *driver;
char *name;
unsigned input_count;
unsigned output_count;
} audio_device_info_t;

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);
Expand Down Expand Up @@ -49,17 +58,52 @@ PHONE_EXPORT phone_status_t phone_hangup_call_id(phone_t instance, const char *c

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 *out, size_t size);
PHONE_EXPORT phone_status_t phone_get_call_id(phone_t instance, int call_index, char *call_id, size_t size);
PHONE_EXPORT phone_status_t phone_get_call_index(phone_t instance, const char *call_id, int *out);

PHONE_EXPORT void phone_refresh_audio_devices();
PHONE_EXPORT size_t phone_get_audio_devices_count();
PHONE_EXPORT size_t phone_get_audio_device_info_name_length();
PHONE_EXPORT phone_status_t phone_get_audio_device_names(char **device_names, size_t *devices_count, size_t max_device_name_length, device_filter_t filter);
PHONE_EXPORT void phone_refresh_audio_devices(void);
PHONE_EXPORT size_t phone_get_audio_devices_count(void);
PHONE_EXPORT size_t phone_get_audio_device_driver_name_length(void);
PHONE_EXPORT size_t phone_get_audio_device_info_name_length(void);
PHONE_DEPRECATED_EXPORT phone_status_t phone_get_audio_device_names(char **device_names, size_t *devices_count, size_t max_device_name_length, device_filter_t filter);

/**
* Get the audio devices that are available in the system.
*
* @param devices Out parameter. Array of phone_audio_device_t
* @param devices_count Out parameter. Number of devices available in the system.
* The value of devices_count might be smaller after the function returned depending
* on the \p filter parameter.
* Can be retrieved via \p phone_get_audio_devices_count.
* @param max_driver_name_length Maximum length of the audio drivers name.
* Can be retrieved via \p phone_get_audio_device_driver_name_length.
* @param max_device_name_length Maximum length of the audio devices name.
* Can be retrieved via \p phone_get_audio_device_info_name_length.
* @param filter Filter the devices that should be returned. \p device_filter_t
*/
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);
PHONE_EXPORT phone_status_t phone_set_audio_devices(int capture_device, int playback_device);

/**
* Check if the SIP INVITE had a \p Call-Info header that included a value like this:
* <pre>
* Call-Info: \<sip:SERVER\>;answer-after=0
* </pre>
* If so the value will be returned via the out parameter \p answer-after.
*
* @param instance phone instance
* @param call_index the call index
* @param answer_after out parameter for the \p answer-after value. -1 if no \p answer-after value exists.
*
* @return phone_status_t
*/
PHONE_EXPORT phone_status_t phone_call_answer_after_index(phone_t instance, int call_index, int *answer_after);
PHONE_EXPORT phone_status_t phone_call_answer_after_id(phone_t instance, const char *call_id, int *answer_after);

PHONE_EXPORT const char* phone_last_error(void);
PHONE_EXPORT void phone_state_name(char *buffer, size_t buffer_size, int state);
PHONE_EXPORT void phone_state_name(char *state_name, size_t buffer_size, int state);
PHONE_EXPORT void phone_set_log_level(int level);

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit fc0fa57

Please sign in to comment.