Add Wear OS SIP client module running baresip NDK stack
- New :wear application module (Wear Compose, minSdk 30) - Second JNI lib 'wearbaresip' reusing distribution static libs - Watch-side service initializes baresip via full init path (libre_init -> conf_configure -> baresip_init -> ua_init) - FGS started from MainActivity foreground state with mic type only (phoneCall requires default-dialer, rejected on watch) - Runtime-verified: builds, installs, native lib loads and SIP stack starts on armeabi-v7a Wear OS device
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,6 +18,8 @@ build
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
/app/release
|
/app/release
|
||||||
/app/.cxx
|
/app/.cxx
|
||||||
|
/wear/.cxx
|
||||||
|
/wear/build
|
||||||
gradle/wrapper/gradle-wrapper.jar
|
gradle/wrapper/gradle-wrapper.jar
|
||||||
/distribution/*
|
/distribution/*
|
||||||
/distribution.video/*
|
/distribution.video/*
|
||||||
|
|||||||
@ -8,6 +8,7 @@ fragmentKtx = "1.8.9"
|
|||||||
gradleVersion = "9.3.1"
|
gradleVersion = "9.3.1"
|
||||||
kotlin = "2.4.10"
|
kotlin = "2.4.10"
|
||||||
kotlinStdlibJdk8 = "2.4.0"
|
kotlinStdlibJdk8 = "2.4.0"
|
||||||
|
android = "2.4.10"
|
||||||
kotlinSerializationPlugin = "2.4.10"
|
kotlinSerializationPlugin = "2.4.10"
|
||||||
kotlinxCoroutinesAndroid = "1.11.0"
|
kotlinxCoroutinesAndroid = "1.11.0"
|
||||||
kotlinxSerializationJson = "1.11.0"
|
kotlinxSerializationJson = "1.11.0"
|
||||||
@ -25,6 +26,8 @@ navigationRuntimeAndroid = "2.9.8"
|
|||||||
composeMaterialIcons = "1.7.8"
|
composeMaterialIcons = "1.7.8"
|
||||||
runtime = "1.11.4"
|
runtime = "1.11.4"
|
||||||
uiText = "1.11.4"
|
uiText = "1.11.4"
|
||||||
|
wear = "1.4.0"
|
||||||
|
wearCompose = "1.4.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||||
@ -54,7 +57,12 @@ androidx-compose-material-icons-core = { group = "androidx.compose.material", na
|
|||||||
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeMaterialIcons" }
|
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeMaterialIcons" }
|
||||||
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
|
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
|
||||||
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
|
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
|
||||||
|
androidx-wear = { module = "androidx.wear:wear", version.ref = "wear" }
|
||||||
|
androidx-wear-compose = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearCompose" }
|
||||||
|
androidx-wear-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearCompose" }
|
||||||
|
androidx-wear-compose-navigation = { group = "androidx.wear.compose", name = "compose-navigation", version.ref = "wearCompose" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerializationPlugin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerializationPlugin" }
|
||||||
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "android" }
|
||||||
|
|||||||
@ -1,5 +1,2 @@
|
|||||||
plugins {
|
include(":app")
|
||||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
include(":wear")
|
||||||
}
|
|
||||||
|
|
||||||
include (":app")
|
|
||||||
|
|||||||
52
wear/build.gradle.kts
Normal file
52
wear/build.gradle.kts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.tutpro.baresip.wear"
|
||||||
|
compileSdk = 37
|
||||||
|
ndkVersion = "29.0.14206865"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.tutpro.baresip.wear"
|
||||||
|
minSdk = 30
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "0.1.0-wear"
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
cFlags += "-DHAVE_INTTYPES_H -lstdc++"
|
||||||
|
arguments.addAll(listOf("-DANDROID_STL=c++_shared"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ndk {
|
||||||
|
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
|
version = "3.31.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(libs.androidx.wear)
|
||||||
|
implementation(libs.androidx.wear.compose)
|
||||||
|
implementation(libs.androidx.wear.compose.foundation)
|
||||||
|
implementation(libs.androidx.wear.compose.navigation)
|
||||||
|
}
|
||||||
41
wear/src/main/AndroidManifest.xml
Normal file
41
wear/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.type.watch" android:required="true" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".WearBaresipApp"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:label="Baresip Wear"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault"
|
||||||
|
tools:targetApi="33">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="Baresip"
|
||||||
|
android:taskAffinity="">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".WearBaresipService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="microphone"
|
||||||
|
android:stopWithTask="false" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
98
wear/src/main/cpp/CMakeLists.txt
Normal file
98
wear/src/main/cpp/CMakeLists.txt
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.18...4.0)
|
||||||
|
|
||||||
|
project(baresip-wear)
|
||||||
|
|
||||||
|
add_link_options("LINKER:--build-id=none")
|
||||||
|
|
||||||
|
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
|
||||||
|
|
||||||
|
add_library(lib_crypto STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
|
||||||
|
|
||||||
|
add_library(lib_ssl STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_ssl PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
|
||||||
|
|
||||||
|
add_library(lib_re STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_re PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/re/lib/${ANDROID_ABI}/libre.a)
|
||||||
|
|
||||||
|
add_library(lib_opus STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/opus/lib/${ANDROID_ABI}/libopus.a)
|
||||||
|
|
||||||
|
add_library(lib_g722 STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_g722 PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/g722/lib/${ANDROID_ABI}/libg722.a)
|
||||||
|
|
||||||
|
add_library(lib_g722_1 STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_g722_1 PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/g7221/lib/${ANDROID_ABI}/libg722_1.a)
|
||||||
|
|
||||||
|
add_library(lib_g729 STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)
|
||||||
|
|
||||||
|
add_library(lib_ilbc STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_ilbc PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/ilbc/lib/${ANDROID_ABI}/libilbc.a)
|
||||||
|
|
||||||
|
add_library(lib_codec2 STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)
|
||||||
|
|
||||||
|
add_library(lib_amrnb STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_amrnb PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrnb.a)
|
||||||
|
|
||||||
|
add_library(lib_amrwb STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_amrwb PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwb.a)
|
||||||
|
|
||||||
|
add_library(lib_amrwbenc STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_amrwbenc PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwbenc.a)
|
||||||
|
|
||||||
|
add_library(lib_zrtpcppcore STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_zrtpcppcore PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/gzrtp/lib/${ANDROID_ABI}/libzrtpcppcore.a)
|
||||||
|
|
||||||
|
add_library(lib_sndfile STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_sndfile PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/sndfile/lib/${ANDROID_ABI}/libsndfile.a)
|
||||||
|
|
||||||
|
add_library(lib_baresip STATIC IMPORTED)
|
||||||
|
set_target_properties(lib_baresip PROPERTIES IMPORTED_LOCATION
|
||||||
|
${distribution_DIR}/baresip/lib/${ANDROID_ABI}/libbaresip.a)
|
||||||
|
|
||||||
|
add_library(wearbaresip SHARED ${CMAKE_SOURCE_DIR}/baresip.c)
|
||||||
|
|
||||||
|
target_include_directories(wearbaresip PRIVATE
|
||||||
|
${distribution_DIR}/openssl/include
|
||||||
|
${distribution_DIR}/re/include
|
||||||
|
${distribution_DIR}/baresip/include)
|
||||||
|
|
||||||
|
add_definitions(-DHAVE_PTHREAD)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
wearbaresip
|
||||||
|
android
|
||||||
|
aaudio
|
||||||
|
lib_baresip
|
||||||
|
lib_re
|
||||||
|
lib_ssl
|
||||||
|
lib_crypto
|
||||||
|
lib_opus
|
||||||
|
lib_g722
|
||||||
|
lib_g722_1
|
||||||
|
lib_g729
|
||||||
|
lib_ilbc
|
||||||
|
lib_codec2
|
||||||
|
lib_amrnb
|
||||||
|
lib_amrwb
|
||||||
|
lib_amrwbenc
|
||||||
|
lib_zrtpcppcore
|
||||||
|
lib_sndfile
|
||||||
|
z
|
||||||
|
log)
|
||||||
466
wear/src/main/cpp/baresip.c
Normal file
466
wear/src/main/cpp/baresip.c
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <aaudio/AAudio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <re.h>
|
||||||
|
#include <baresip.h>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
typedef struct baresip_context
|
||||||
|
{
|
||||||
|
JavaVM *javaVM;
|
||||||
|
jclass serviceClz;
|
||||||
|
jobject serviceObj;
|
||||||
|
|
||||||
|
} BaresipContext;
|
||||||
|
|
||||||
|
static BaresipContext g_ctx;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ASYNC_WORKERS = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
static pthread_key_t g_thread_key;
|
||||||
|
|
||||||
|
static void detach_thread(void *env)
|
||||||
|
{
|
||||||
|
(void)env;
|
||||||
|
(*g_ctx.javaVM)->DetachCurrentThread(g_ctx.javaVM);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JNIEnv *get_jni_env(void)
|
||||||
|
{
|
||||||
|
JNIEnv *env;
|
||||||
|
jint res = (*g_ctx.javaVM)->GetEnv(g_ctx.javaVM, (void**)&env, JNI_VERSION_1_6);
|
||||||
|
if (res != JNI_OK) {
|
||||||
|
res = (*g_ctx.javaVM)->AttachCurrentThread(g_ctx.javaVM, &env, NULL);
|
||||||
|
if (res == JNI_OK) {
|
||||||
|
pthread_setspecific(g_thread_key, env);
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ua_exit_handler(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
re_cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *translate_errorcode(uint16_t scode)
|
||||||
|
{
|
||||||
|
switch (scode) {
|
||||||
|
case 404:
|
||||||
|
return "";
|
||||||
|
case 486:
|
||||||
|
case 603:
|
||||||
|
return "busy";
|
||||||
|
case 487:
|
||||||
|
return "";
|
||||||
|
default:
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
const char *prm = bevent_get_text(event);
|
||||||
|
struct call *call = bevent_get_call(event);
|
||||||
|
struct ua *ua = bevent_get_ua(event);
|
||||||
|
const struct sip_msg *msg = bevent_get_msg(event);
|
||||||
|
struct account *acc = ua_account(bevent_get_ua(event));
|
||||||
|
const char *tone;
|
||||||
|
char event_buf[256];
|
||||||
|
enum sdp_dir ardir;
|
||||||
|
int len, err;
|
||||||
|
struct pl module, module_event, data;
|
||||||
|
|
||||||
|
switch (ev) {
|
||||||
|
case BEVENT_CREATE:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "create", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_REGISTERING:
|
||||||
|
case BEVENT_UNREGISTERING:
|
||||||
|
case BEVENT_REGISTER_OK:
|
||||||
|
case BEVENT_FALLBACK_OK:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_REGISTER_FAIL:
|
||||||
|
case BEVENT_FALLBACK_FAIL:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "registering failed,%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_SIPSESS_CONN:
|
||||||
|
ua = uag_find_msg(msg);
|
||||||
|
call = (struct call *)msg;
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "%s,%r,%ld", prm, &msg->from.auri, (long)event);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_INCOMING:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call incoming,%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_OUTGOING:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call outgoing", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_ANSWERED:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call answered", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_REDIRECT:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call redirect,%s", prm + 4);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_LOCAL_SDP:
|
||||||
|
if (strcmp(prm, "offer") == 0)
|
||||||
|
return;
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call %sed", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_RINGING:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call ringing", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_PROGRESS:
|
||||||
|
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call progress,%d", ardir);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_ESTABLISHED:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call established", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_REMOTE_SDP:
|
||||||
|
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call update,%d,%ld", ardir, (long)event);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_MENC:
|
||||||
|
if (prm[0] == '0')
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call secure", "");
|
||||||
|
else if (prm[0] == '1')
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call verify,%s", prm + 2);
|
||||||
|
else if (prm[0] == '2')
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call verified,%s", prm + 2);
|
||||||
|
else
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "unknown menc event", "");
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_TRANSFER:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call transfer,%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_TRANSFER_FAILED:
|
||||||
|
call_hold(call, false);
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "transfer failed,%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_CALL_CLOSED:
|
||||||
|
tone = call_scode(call) ? translate_errorcode(call_scode(call)) : "";
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "call closed,%s,%s", prm, tone);
|
||||||
|
break;
|
||||||
|
case BEVENT_MWI_NOTIFY:
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "mwi notify,%s", prm);
|
||||||
|
break;
|
||||||
|
case BEVENT_MODULE:
|
||||||
|
err = re_regex(prm, strlen(prm), "[^,]*,[^,]*,[~]*", &module, &module_event, &data);
|
||||||
|
if (err)
|
||||||
|
return;
|
||||||
|
if (!pl_strcmp(&module_event, "dump")) {
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "sndfile dump,%r", &data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!pl_strcmp(&module_event, "recorder sessionid")) {
|
||||||
|
len = re_snprintf(event_buf, sizeof event_buf, "recorder sessionid,%r", &data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
if (!env) return;
|
||||||
|
|
||||||
|
if (!g_ctx.serviceClz || !g_ctx.serviceObj) return;
|
||||||
|
|
||||||
|
jmethodID methodId =
|
||||||
|
(*env)->GetMethodID(env, g_ctx.serviceClz, "uaEvent", "(Ljava/lang/String;)V");
|
||||||
|
jstring jEvent = (*env)->NewStringUTF(env, event_buf);
|
||||||
|
if (methodId) {
|
||||||
|
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, jEvent);
|
||||||
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
|
(*env)->ExceptionDescribe(env);
|
||||||
|
(*env)->ExceptionClear(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*env)->DeleteLocalRef(env, jEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void message_handler(
|
||||||
|
struct ua *ua, const struct pl *peer, const struct pl *ctype, struct mbuf *body, void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
char peer_buf[256];
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
if (snprintf(peer_buf, 256, "%.*s", (int)peer->l, peer->p) >= 256) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
if (!env) return;
|
||||||
|
|
||||||
|
if (!g_ctx.serviceClz || !g_ctx.serviceObj) return;
|
||||||
|
|
||||||
|
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.serviceClz, "messageEvent",
|
||||||
|
"(JLjava/lang/String;Ljava/lang/String;[B)V");
|
||||||
|
jstring jPeer = (*env)->NewStringUTF(env, peer_buf);
|
||||||
|
jbyteArray jMsg;
|
||||||
|
size = mbuf_get_left(body);
|
||||||
|
jMsg = (*env)->NewByteArray(env, (jsize)size);
|
||||||
|
if ((*env)->GetArrayLength(env, jMsg) != size) {
|
||||||
|
(*env)->DeleteLocalRef(env, jMsg);
|
||||||
|
jMsg = (*env)->NewByteArray(env, (jsize)size);
|
||||||
|
}
|
||||||
|
void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)jMsg, 0);
|
||||||
|
memcpy(temp, mbuf_buf(body), size);
|
||||||
|
(*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);
|
||||||
|
if (methodId) {
|
||||||
|
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, (jlong)ua, jPeer, NULL, jMsg);
|
||||||
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
|
(*env)->ExceptionDescribe(env);
|
||||||
|
(*env)->ExceptionClear(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*env)->DeleteLocalRef(env, jPeer);
|
||||||
|
(*env)->DeleteLocalRef(env, jMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
|
||||||
|
{
|
||||||
|
char *native_time = (char *)arg;
|
||||||
|
char reason_buf[64];
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
mem_deref(native_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pl_strcpy(&(msg->reason), reason_buf, 64);
|
||||||
|
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
if (!env) {
|
||||||
|
mem_deref(native_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_ctx.serviceClz || !g_ctx.serviceObj) {
|
||||||
|
mem_deref(native_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.serviceClz, "messageResponse",
|
||||||
|
"(ILjava/lang/String;Ljava/lang/String;)V");
|
||||||
|
jstring javaReason = (*env)->NewStringUTF(env, reason_buf);
|
||||||
|
jstring javaTime = (*env)->NewStringUTF(env, native_time);
|
||||||
|
if (methodId) {
|
||||||
|
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, msg->scode, javaReason,
|
||||||
|
javaTime);
|
||||||
|
if ((*env)->ExceptionCheck(env)) {
|
||||||
|
(*env)->ExceptionDescribe(env);
|
||||||
|
(*env)->ExceptionClear(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*env)->DeleteLocalRef(env, javaReason);
|
||||||
|
(*env)->DeleteLocalRef(env, javaTime);
|
||||||
|
|
||||||
|
mem_deref(native_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ID_UA_STOP_ALL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mqueue *mq;
|
||||||
|
|
||||||
|
static void mqueue_handler(int id, void *data, void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
if (id == ID_UA_STOP_ALL) {
|
||||||
|
ua_stop_all((bool)(uintptr_t)data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static int pfd[2];
|
||||||
|
static pthread_t loggingThread;
|
||||||
|
|
||||||
|
static void *loggingFunction(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
ssize_t readSize;
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
while ((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) {
|
||||||
|
if (buf[readSize - 1] == '\n') {
|
||||||
|
--readSize;
|
||||||
|
}
|
||||||
|
buf[readSize] = 0;
|
||||||
|
LOGD("%s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int runLoggingThread()
|
||||||
|
{
|
||||||
|
setvbuf(stdout, 0, _IOLBF, 0);
|
||||||
|
setvbuf(stderr, 0, _IONBF, 0);
|
||||||
|
|
||||||
|
pipe(pfd);
|
||||||
|
dup2(pfd[1], 1);
|
||||||
|
dup2(pfd[1], 2);
|
||||||
|
|
||||||
|
int ret = pthread_create(&loggingThread, NULL, loggingFunction, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_detach(loggingThread);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
|
||||||
|
{
|
||||||
|
(void)reserved;
|
||||||
|
|
||||||
|
memset(&g_ctx, 0, sizeof(g_ctx));
|
||||||
|
g_ctx.javaVM = vm;
|
||||||
|
|
||||||
|
if (pthread_key_create(&g_thread_key, detach_thread) != 0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_tutpro_baresip_wear_WearBaresipService_baresipStart(
|
||||||
|
JNIEnv *env, jobject instance, jstring jPath, jstring jAddrs, jint jLogLevel, jstring jSoftware)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
jclass clz = (*env)->GetObjectClass(env, instance);
|
||||||
|
g_ctx.serviceClz = (*env)->NewGlobalRef(env, clz);
|
||||||
|
g_ctx.serviceObj = (*env)->NewGlobalRef(env, instance);
|
||||||
|
|
||||||
|
const char *path = (*env)->GetStringUTFChars(env, jPath, 0);
|
||||||
|
const char *addrs = (*env)->GetStringUTFChars(env, jAddrs, 0);
|
||||||
|
const char *software = (*env)->GetStringUTFChars(env, jSoftware, 0);
|
||||||
|
|
||||||
|
runLoggingThread();
|
||||||
|
|
||||||
|
err = libre_init();
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (re_thread_check(true) == 0) {
|
||||||
|
jint res = (*g_ctx.javaVM)->AttachCurrentThread(g_ctx.javaVM, &env, NULL);
|
||||||
|
if (JNI_OK != res) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf_path_set(path);
|
||||||
|
|
||||||
|
log_level_set((enum log_level)jLogLevel);
|
||||||
|
|
||||||
|
err = conf_configure();
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
re_thread_async_init(ASYNC_WORKERS);
|
||||||
|
|
||||||
|
err = baresip_init(conf_config());
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsc_cache_max(net_dnsc(baresip_network()), 0);
|
||||||
|
|
||||||
|
if (strlen(addrs) > 0) {
|
||||||
|
char *addr_list = (char *)malloc(strlen(addrs) + 1);
|
||||||
|
struct sa temp_sa;
|
||||||
|
char buf[256];
|
||||||
|
net_flush_addresses(baresip_network());
|
||||||
|
strcpy(addr_list, addrs);
|
||||||
|
char *ptr = strtok(addr_list, ";");
|
||||||
|
while (ptr != NULL) {
|
||||||
|
if (0 == sa_set_str(&temp_sa, ptr, 0)) {
|
||||||
|
sa_ntop(&temp_sa, buf, 256);
|
||||||
|
ptr = strtok(NULL, ";");
|
||||||
|
net_add_address_ifname(baresip_network(), &temp_sa, ptr);
|
||||||
|
} else {
|
||||||
|
ptr = strtok(NULL, ";");
|
||||||
|
}
|
||||||
|
if (ptr)
|
||||||
|
*(ptr - 1) = ';';
|
||||||
|
ptr = strtok(NULL, ";");
|
||||||
|
}
|
||||||
|
free(addr_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ua_init(software, true, true, true);
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
uag_set_exit_handler(ua_exit_handler, NULL);
|
||||||
|
|
||||||
|
err = bevent_register(event_handler, NULL);
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = message_listen(baresip_message(), message_handler, NULL);
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conf_modules();
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mqueue_alloc(&mq, mqueue_handler, NULL);
|
||||||
|
if (err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jPath, path);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jAddrs, addrs);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, jSoftware, software);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_tutpro_baresip_wear_WearBaresipService_baresipStop(JNIEnv *env, jobject instance)
|
||||||
|
{
|
||||||
|
(void)env;
|
||||||
|
(void)instance;
|
||||||
|
|
||||||
|
ua_stop_all(true);
|
||||||
|
baresip_close();
|
||||||
|
libre_close();
|
||||||
|
|
||||||
|
if (g_ctx.serviceClz) {
|
||||||
|
(*env)->DeleteGlobalRef(env, g_ctx.serviceClz);
|
||||||
|
g_ctx.serviceClz = NULL;
|
||||||
|
}
|
||||||
|
if (g_ctx.serviceObj) {
|
||||||
|
(*env)->DeleteGlobalRef(env, g_ctx.serviceObj);
|
||||||
|
g_ctx.serviceObj = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
wear/src/main/cpp/logger.h
Normal file
13
wear/src/main/cpp/logger.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include <baresip.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#ifndef BARESIP_LOGGER_H
|
||||||
|
#define BARESIP_LOGGER_H
|
||||||
|
|
||||||
|
#define LOG_TAG "Baresip Wear"
|
||||||
|
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
|
||||||
|
|
||||||
|
#endif
|
||||||
49
wear/src/main/java/com/tutpro/baresip/wear/MainActivity.kt
Normal file
49
wear/src/main/java/com/tutpro/baresip/wear/MainActivity.kt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package com.tutpro.baresip.wear
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.wear.compose.material.Text
|
||||||
|
import androidx.wear.compose.material.TimeText
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val recordAudioPermission = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
if (granted) {
|
||||||
|
startBaresipService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContent {
|
||||||
|
TimeText()
|
||||||
|
Text(text = "Baresip Wear")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
|
||||||
|
== PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
startBaresipService()
|
||||||
|
} else {
|
||||||
|
recordAudioPermission.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startBaresipService() {
|
||||||
|
val intent = Intent(this, WearBaresipService::class.java)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
wear/src/main/java/com/tutpro/baresip/wear/WearBaresipApp.kt
Normal file
10
wear/src/main/java/com/tutpro/baresip/wear/WearBaresipApp.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.tutpro.baresip.wear
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
|
||||||
|
class WearBaresipApp : Application() {
|
||||||
|
// The foreground service is launched from MainActivity (foreground state),
|
||||||
|
// not from Application.onCreate(), because starting a microphone-type
|
||||||
|
// foreground service from a non-foreground context is rejected on
|
||||||
|
// targetSdk 34+.
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.tutpro.baresip.wear
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
|
class WearBaresipService : Service() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"Baresip Wear",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle("Baresip Wear")
|
||||||
|
.setContentText("SIP stack running")
|
||||||
|
.setSmallIcon(android.R.drawable.sym_call_incoming)
|
||||||
|
.build()
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
|
||||||
|
// Start the native baresip stack. Path points at the app's private
|
||||||
|
// config directory where accounts.cfg etc. are expected.
|
||||||
|
val path = applicationContext.filesDir.absolutePath
|
||||||
|
baresipStart(path, "", 2, "baresip-studio-wear")
|
||||||
|
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
baresipStop()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
external fun baresipStart(path: String, addrs: String, logLevel: Int, software: String)
|
||||||
|
external fun baresipStop()
|
||||||
|
|
||||||
|
fun uaEvent(event: String) {
|
||||||
|
// TODO route to watch UI / Data Layer
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID = "baresip-wear"
|
||||||
|
const val NOTIFICATION_ID = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("wearbaresip")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user