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
|
||||
/app/release
|
||||
/app/.cxx
|
||||
/wear/.cxx
|
||||
/wear/build
|
||||
gradle/wrapper/gradle-wrapper.jar
|
||||
/distribution/*
|
||||
/distribution.video/*
|
||||
|
||||
@ -8,6 +8,7 @@ fragmentKtx = "1.8.9"
|
||||
gradleVersion = "9.3.1"
|
||||
kotlin = "2.4.10"
|
||||
kotlinStdlibJdk8 = "2.4.0"
|
||||
android = "2.4.10"
|
||||
kotlinSerializationPlugin = "2.4.10"
|
||||
kotlinxCoroutinesAndroid = "1.11.0"
|
||||
kotlinxSerializationJson = "1.11.0"
|
||||
@ -25,6 +26,8 @@ navigationRuntimeAndroid = "2.9.8"
|
||||
composeMaterialIcons = "1.7.8"
|
||||
runtime = "1.11.4"
|
||||
uiText = "1.11.4"
|
||||
wear = "1.4.0"
|
||||
wearCompose = "1.4.1"
|
||||
|
||||
[libraries]
|
||||
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-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-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]
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
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 {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
include (":app")
|
||||
include(":app")
|
||||
include(":wear")
|
||||
|
||||
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