Fangjun Kuang
Committed by GitHub

Android demo for speaker diarization (#1423)

正在显示 64 个修改的文件 包含 1893 行增加14 行删除
  1 +name: apk-speaker-diarization
  2 +
  3 +on:
  4 + push:
  5 + branches:
  6 + - apk
  7 + - android-demo-speaker-diarization-2
  8 +
  9 + workflow_dispatch:
  10 +
  11 +concurrency:
  12 + group: apk-speaker-diarization-${{ github.ref }}
  13 + cancel-in-progress: true
  14 +
  15 +permissions:
  16 + contents: write
  17 +
  18 +jobs:
  19 + apk_speaker_identification:
  20 + if: github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa'
  21 + runs-on: ${{ matrix.os }}
  22 + name: apk for speaker diarization ${{ matrix.index }}/${{ matrix.total }}
  23 + strategy:
  24 + fail-fast: false
  25 + matrix:
  26 + os: [ubuntu-latest]
  27 + total: ["1"]
  28 + index: ["0"]
  29 +
  30 + steps:
  31 + - uses: actions/checkout@v4
  32 + with:
  33 + fetch-depth: 0
  34 +
  35 + # https://github.com/actions/setup-java
  36 + - uses: actions/setup-java@v4
  37 + with:
  38 + distribution: 'temurin' # See 'Supported distributions' for available options
  39 + java-version: '21'
  40 +
  41 + - name: ccache
  42 + uses: hendrikmuhs/ccache-action@v1.2
  43 + with:
  44 + key: ${{ matrix.os }}-android
  45 +
  46 + - name: Display NDK HOME
  47 + shell: bash
  48 + run: |
  49 + echo "ANDROID_NDK_LATEST_HOME: ${ANDROID_NDK_LATEST_HOME}"
  50 + ls -lh ${ANDROID_NDK_LATEST_HOME}
  51 +
  52 + - name: Install Python dependencies
  53 + shell: bash
  54 + run: |
  55 + python3 -m pip install --upgrade pip jinja2
  56 +
  57 + - name: Setup build tool version variable
  58 + shell: bash
  59 + run: |
  60 + echo "---"
  61 + ls -lh /usr/local/lib/android/
  62 + echo "---"
  63 +
  64 + ls -lh /usr/local/lib/android/sdk
  65 + echo "---"
  66 +
  67 + ls -lh /usr/local/lib/android/sdk/build-tools
  68 + echo "---"
  69 +
  70 + BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
  71 + echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
  72 + echo "Last build tool version is: $BUILD_TOOL_VERSION"
  73 +
  74 + - name: Generate build script
  75 + shell: bash
  76 + run: |
  77 + cd scripts/apk
  78 +
  79 + chmod +x build-apk-speaker-diarization.sh
  80 + mv -v ./build-apk-speaker-diarization.sh ../..
  81 +
  82 + - name: build APK
  83 + shell: bash
  84 + run: |
  85 + export CMAKE_CXX_COMPILER_LAUNCHER=ccache
  86 + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
  87 + cmake --version
  88 +
  89 + export ANDROID_NDK=$ANDROID_NDK_LATEST_HOME
  90 + ./build-apk-speaker-diarization.sh
  91 +
  92 + - name: Display APK
  93 + shell: bash
  94 + run: |
  95 + ls -lh ./apks/
  96 + du -h -d1 .
  97 +
  98 + # https://github.com/marketplace/actions/sign-android-release
  99 + - uses: r0adkll/sign-android-release@v1
  100 + name: Sign app APK
  101 + with:
  102 + releaseDirectory: ./apks
  103 + signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
  104 + alias: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
  105 + keyStorePassword: ${{ secrets.ANDROID_SIGNING_KEY_STORE_PASSWORD }}
  106 + env:
  107 + BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
  108 +
  109 + - name: Display APK after signing
  110 + shell: bash
  111 + run: |
  112 + ls -lh ./apks/
  113 + du -h -d1 .
  114 +
  115 + - name: Rename APK after signing
  116 + shell: bash
  117 + run: |
  118 + cd apks
  119 + rm -fv signingKey.jks
  120 + rm -fv *.apk.idsig
  121 + rm -fv *-aligned.apk
  122 +
  123 + all_apks=$(ls -1 *-signed.apk)
  124 + echo "----"
  125 + echo $all_apks
  126 + echo "----"
  127 + for apk in ${all_apks[@]}; do
  128 + n=$(echo $apk | sed -e s/-signed//)
  129 + mv -v $apk $n
  130 + done
  131 +
  132 + cd ..
  133 +
  134 + ls -lh ./apks/
  135 + du -h -d1 .
  136 +
  137 + - name: Display APK after rename
  138 + shell: bash
  139 + run: |
  140 + ls -lh ./apks/
  141 + du -h -d1 .
  142 +
  143 + - name: Publish to huggingface
  144 + env:
  145 + HF_TOKEN: ${{ secrets.HF_TOKEN }}
  146 + uses: nick-fields/retry@v3
  147 + with:
  148 + max_attempts: 20
  149 + timeout_seconds: 200
  150 + shell: bash
  151 + command: |
  152 + git config --global user.email "csukuangfj@gmail.com"
  153 + git config --global user.name "Fangjun Kuang"
  154 +
  155 + rm -rf huggingface
  156 + export GIT_LFS_SKIP_SMUDGE=1
  157 + export GIT_CLONE_PROTECTION_ACTIVE=false
  158 +
  159 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  160 + echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION"
  161 +
  162 + git clone https://huggingface.co/csukuangfj/sherpa-onnx-apk huggingface
  163 + cd huggingface
  164 + git fetch
  165 + git pull
  166 + git merge -m "merge remote" --ff origin main
  167 +
  168 + d=speaker-diarization/$SHERPA_ONNX_VERSION
  169 + mkdir -p $d/
  170 + cp -v ../apks/*.apk $d/
  171 + git status
  172 + git lfs track "*.apk"
  173 + git add .
  174 + git commit -m "add more apks"
  175 + git push https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-apk main
@@ -53,6 +53,23 @@ jobs: @@ -53,6 +53,23 @@ jobs:
53 run: | 53 run: |
54 python3 -m pip install --upgrade pip jinja2 54 python3 -m pip install --upgrade pip jinja2
55 55
  56 + - name: Setup build tool version variable
  57 + shell: bash
  58 + run: |
  59 + echo "---"
  60 + ls -lh /usr/local/lib/android/
  61 + echo "---"
  62 +
  63 + ls -lh /usr/local/lib/android/sdk
  64 + echo "---"
  65 +
  66 + ls -lh /usr/local/lib/android/sdk/build-tools
  67 + echo "---"
  68 +
  69 + BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
  70 + echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
  71 + echo "Last build tool version is: $BUILD_TOOL_VERSION"
  72 +
56 - name: Generate build script 73 - name: Generate build script
57 shell: bash 74 shell: bash
58 run: | 75 run: |
@@ -82,6 +99,51 @@ jobs: @@ -82,6 +99,51 @@ jobs:
82 ls -lh ./apks/ 99 ls -lh ./apks/
83 du -h -d1 . 100 du -h -d1 .
84 101
  102 + # https://github.com/marketplace/actions/sign-android-release
  103 + - uses: r0adkll/sign-android-release@v1
  104 + name: Sign app APK
  105 + with:
  106 + releaseDirectory: ./apks
  107 + signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
  108 + alias: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
  109 + keyStorePassword: ${{ secrets.ANDROID_SIGNING_KEY_STORE_PASSWORD }}
  110 + env:
  111 + BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
  112 +
  113 + - name: Display APK after signing
  114 + shell: bash
  115 + run: |
  116 + ls -lh ./apks/
  117 + du -h -d1 .
  118 +
  119 + - name: Rename APK after signing
  120 + shell: bash
  121 + run: |
  122 + cd apks
  123 + rm -fv signingKey.jks
  124 + rm -fv *.apk.idsig
  125 + rm -fv *-aligned.apk
  126 +
  127 + all_apks=$(ls -1 *-signed.apk)
  128 + echo "----"
  129 + echo $all_apks
  130 + echo "----"
  131 + for apk in ${all_apks[@]}; do
  132 + n=$(echo $apk | sed -e s/-signed//)
  133 + mv -v $apk $n
  134 + done
  135 +
  136 + cd ..
  137 +
  138 + ls -lh ./apks/
  139 + du -h -d1 .
  140 +
  141 + - name: Display APK after rename
  142 + shell: bash
  143 + run: |
  144 + ls -lh ./apks/
  145 + du -h -d1 .
  146 +
85 - name: Publish to huggingface 147 - name: Publish to huggingface
86 env: 148 env:
87 HF_TOKEN: ${{ secrets.HF_TOKEN }} 149 HF_TOKEN: ${{ secrets.HF_TOKEN }}
@@ -166,7 +166,7 @@ jobs: @@ -166,7 +166,7 @@ jobs:
166 git pull 166 git pull
167 git merge -m "merge remote" --ff origin main 167 git merge -m "merge remote" --ff origin main
168 168
169 - d=vad/SHERPA_ONNX_VERSION 169 + d=vad/$SHERPA_ONNX_VERSION
170 mkdir -p $d 170 mkdir -p $d
171 cp -v ../apks/*.apk $d/ 171 cp -v ../apks/*.apk $d/
172 git status 172 git status
@@ -84,8 +84,9 @@ with the following APIs @@ -84,8 +84,9 @@ with the following APIs
84 84
85 ### Links for Huggingface Spaces 85 ### Links for Huggingface Spaces
86 86
87 -You can visit the following Huggingface spaces to try `sherpa-onnx` without  
88 -installing anything. All you need is a browser. 87 +<details>
  88 +<summary>You can visit the following Huggingface spaces to try sherpa-onnx without
  89 +installing anything. All you need is a browser.</summary>
89 90
90 | Description | URL | 91 | Description | URL |
91 |-------------------------------------------------------|------------------------------------| 92 |-------------------------------------------------------|------------------------------------|
@@ -118,11 +119,18 @@ We also have spaces built using WebAssembly. They are listed below: @@ -118,11 +119,18 @@ We also have spaces built using WebAssembly. They are listed below:
118 |Speech synthesis (German) |[Click me][wasm-hf-tts-piper-de]| [地址][wasm-ms-tts-piper-de]| 119 |Speech synthesis (German) |[Click me][wasm-hf-tts-piper-de]| [地址][wasm-ms-tts-piper-de]|
119 |Speaker diarization |[Click me][wasm-hf-speaker-diarization]|[地址][wasm-ms-speaker-diarization]| 120 |Speaker diarization |[Click me][wasm-hf-speaker-diarization]|[地址][wasm-ms-speaker-diarization]|
120 121
  122 +</details>
  123 +
121 ### Links for pre-built Android APKs 124 ### Links for pre-built Android APKs
122 125
  126 +<details>
  127 +
  128 +<summary>You can find pre-built Android APKs for this repository in the following table</summary>
  129 +
123 | Description | URL | 中国用户 | 130 | Description | URL | 中国用户 |
124 -|----------------------------------------|------------------------------|-----------------------------|  
125 -| Streaming speech recognition | [Address][apk-streaming-asr] | [点此][apk-streaming-asr-cn]| 131 +|----------------------------------------|------------------------------------|-----------------------------------|
  132 +| Speaker diarization | [Address][apk-speaker-diarization] | [点此][apk-speaker-diarization-cn]|
  133 +| Streaming speech recognition | [Address][apk-streaming-asr] | [点此][apk-streaming-asr-cn] |
126 | Text-to-speech | [Address][apk-tts] | [点此][apk-tts-cn] | 134 | Text-to-speech | [Address][apk-tts] | [点此][apk-tts-cn] |
127 | Voice activity detection (VAD) | [Address][apk-vad] | [点此][apk-vad-cn] | 135 | Voice activity detection (VAD) | [Address][apk-vad] | [点此][apk-vad-cn] |
128 | VAD + non-streaming speech recognition | [Address][apk-vad-asr] | [点此][apk-vad-asr-cn] | 136 | VAD + non-streaming speech recognition | [Address][apk-vad-asr] | [点此][apk-vad-asr-cn] |
@@ -133,8 +141,12 @@ We also have spaces built using WebAssembly. They are listed below: @@ -133,8 +141,12 @@ We also have spaces built using WebAssembly. They are listed below:
133 | Spoken language identification | [Address][apk-slid] | [点此][apk-slid-cn] | 141 | Spoken language identification | [Address][apk-slid] | [点此][apk-slid-cn] |
134 | Keyword spotting | [Address][apk-kws] | [点此][apk-kws-cn] | 142 | Keyword spotting | [Address][apk-kws] | [点此][apk-kws-cn] |
135 143
  144 +</details>
  145 +
136 ### Links for pre-built Flutter APPs 146 ### Links for pre-built Flutter APPs
137 147
  148 +<details>
  149 +
138 #### Real-time speech recognition 150 #### Real-time speech recognition
139 151
140 | Description | URL | 中国用户 | 152 | Description | URL | 中国用户 |
@@ -153,17 +165,24 @@ We also have spaces built using WebAssembly. They are listed below: @@ -153,17 +165,24 @@ We also have spaces built using WebAssembly. They are listed below:
153 165
154 > Note: You need to build from source for iOS. 166 > Note: You need to build from source for iOS.
155 167
  168 +</details>
  169 +
156 ### Links for pre-built Lazarus APPs 170 ### Links for pre-built Lazarus APPs
157 171
  172 +<details>
  173 +
158 #### Generating subtitles 174 #### Generating subtitles
159 175
160 | Description | URL | 中国用户 | 176 | Description | URL | 中国用户 |
161 |--------------------------------|----------------------------|----------------------------| 177 |--------------------------------|----------------------------|----------------------------|
162 | Generate subtitles (生成字幕) | [Address][lazarus-subtitle]| [点此][lazarus-subtitle-cn]| 178 | Generate subtitles (生成字幕) | [Address][lazarus-subtitle]| [点此][lazarus-subtitle-cn]|
163 179
  180 +</details>
164 181
165 ### Links for pre-trained models 182 ### Links for pre-trained models
166 183
  184 +<details>
  185 +
167 | Description | URL | 186 | Description | URL |
168 |---------------------------------------------|---------------------------------------------------------------------------------------| 187 |---------------------------------------------|---------------------------------------------------------------------------------------|
169 | Speech recognition (speech to text, ASR) | [Address][asr-models] | 188 | Speech recognition (speech to text, ASR) | [Address][asr-models] |
@@ -176,6 +195,8 @@ We also have spaces built using WebAssembly. They are listed below: @@ -176,6 +195,8 @@ We also have spaces built using WebAssembly. They are listed below:
176 | Punctuation | [Address][punct-models] | 195 | Punctuation | [Address][punct-models] |
177 | Speaker segmentation | [Address][speaker-segmentation-models] | 196 | Speaker segmentation | [Address][speaker-segmentation-models] |
178 197
  198 +</details>
  199 +
179 ### Useful links 200 ### Useful links
180 201
181 - Documentation: https://k2-fsa.github.io/sherpa/onnx/ 202 - Documentation: https://k2-fsa.github.io/sherpa/onnx/
@@ -265,6 +286,8 @@ Video demo in Chinese: [爆了ï¼ç‚«ç¥žæ•™ä½ å¼€æ‰“字挂ï¼çœŸæ­£å½±å“èƒœçŽ‡çš @@ -265,6 +286,8 @@ Video demo in Chinese: [爆了ï¼ç‚«ç¥žæ•™ä½ å¼€æ‰“字挂ï¼çœŸæ­£å½±å“胜率çš
265 [wasm-ms-tts-piper-de]: https://modelscope.cn/studios/k2-fsa/web-assembly-tts-sherpa-onnx-de 286 [wasm-ms-tts-piper-de]: https://modelscope.cn/studios/k2-fsa/web-assembly-tts-sherpa-onnx-de
266 [wasm-hf-speaker-diarization]: https://huggingface.co/spaces/k2-fsa/web-assembly-speaker-diarization-sherpa-onnx 287 [wasm-hf-speaker-diarization]: https://huggingface.co/spaces/k2-fsa/web-assembly-speaker-diarization-sherpa-onnx
267 [wasm-ms-speaker-diarization]: https://www.modelscope.cn/studios/csukuangfj/web-assembly-speaker-diarization-sherpa-onnx 288 [wasm-ms-speaker-diarization]: https://www.modelscope.cn/studios/csukuangfj/web-assembly-speaker-diarization-sherpa-onnx
  289 +[apk-speaker-diarization]: https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/apk.html
  290 +[apk-speaker-diarization-cn]: https://k2-fsa.github.io/sherpa/onnx/speaker-diarization/apk-cn.html
268 [apk-streaming-asr]: https://k2-fsa.github.io/sherpa/onnx/android/apk.html 291 [apk-streaming-asr]: https://k2-fsa.github.io/sherpa/onnx/android/apk.html
269 [apk-streaming-asr-cn]: https://k2-fsa.github.io/sherpa/onnx/android/apk-cn.html 292 [apk-streaming-asr-cn]: https://k2-fsa.github.io/sherpa/onnx/android/apk-cn.html
270 [apk-tts]: https://k2-fsa.github.io/sherpa/onnx/tts/apk-engine.html 293 [apk-tts]: https://k2-fsa.github.io/sherpa/onnx/tts/apk-engine.html
@@ -4,6 +4,8 @@ Please refer to @@ -4,6 +4,8 @@ Please refer to
4 https://k2-fsa.github.io/sherpa/onnx/android/index.html 4 https://k2-fsa.github.io/sherpa/onnx/android/index.html
5 for usage. 5 for usage.
6 6
  7 +- [SherpaOnnxSpeakerDiarization](./SherpaOnnxSpeakerDiarization) It is for speaker diarization.
  8 +
7 - [SherpaOnnx](./SherpaOnnx) It uses a streaming ASR model. 9 - [SherpaOnnx](./SherpaOnnx) It uses a streaming ASR model.
8 10
9 - [SherpaOnnx2Pass](./SherpaOnnx2Pass) It uses a streaming ASR model 11 - [SherpaOnnx2Pass](./SherpaOnnx2Pass) It uses a streaming ASR model
  1 +*.iml
  2 +.gradle
  3 +/local.properties
  4 +/.idea/caches
  5 +/.idea/libraries
  6 +/.idea/modules.xml
  7 +/.idea/workspace.xml
  8 +/.idea/navEditor.xml
  9 +/.idea/assetWizardSettings.xml
  10 +.DS_Store
  11 +/build
  12 +/captures
  13 +.externalNativeBuild
  14 +.cxx
  15 +local.properties
  1 +plugins {
  2 + alias(libs.plugins.android.application)
  3 + alias(libs.plugins.jetbrains.kotlin.android)
  4 +}
  5 +
  6 +android {
  7 + namespace = "com.k2fsa.sherpa.onnx.speaker.diarization"
  8 + compileSdk = 34
  9 +
  10 + defaultConfig {
  11 + applicationId = "com.k2fsa.sherpa.onnx.speaker.diarization"
  12 + minSdk = 21
  13 + targetSdk = 34
  14 + versionCode = 1
  15 + versionName = "1.0"
  16 +
  17 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
  18 + vectorDrawables {
  19 + useSupportLibrary = true
  20 + }
  21 + }
  22 +
  23 + buildTypes {
  24 + release {
  25 + isMinifyEnabled = false
  26 + proguardFiles(
  27 + getDefaultProguardFile("proguard-android-optimize.txt"),
  28 + "proguard-rules.pro"
  29 + )
  30 + }
  31 + }
  32 + compileOptions {
  33 + sourceCompatibility = JavaVersion.VERSION_1_8
  34 + targetCompatibility = JavaVersion.VERSION_1_8
  35 + }
  36 + kotlinOptions {
  37 + jvmTarget = "1.8"
  38 + }
  39 + buildFeatures {
  40 + compose = true
  41 + }
  42 + composeOptions {
  43 + kotlinCompilerExtensionVersion = "1.5.1"
  44 + }
  45 + packaging {
  46 + resources {
  47 + excludes += "/META-INF/{AL2.0,LGPL2.1}"
  48 + }
  49 + }
  50 +}
  51 +
  52 +dependencies {
  53 +
  54 + implementation(libs.androidx.core.ktx)
  55 + implementation(libs.androidx.lifecycle.runtime.ktx)
  56 + implementation(libs.androidx.activity.compose)
  57 + implementation(platform(libs.androidx.compose.bom))
  58 + implementation(libs.androidx.ui)
  59 + implementation(libs.androidx.ui.graphics)
  60 + implementation(libs.androidx.ui.tooling.preview)
  61 + implementation(libs.androidx.material3)
  62 + implementation(libs.androidx.navigation.compose)
  63 + implementation(libs.androidx.documentfile)
  64 + testImplementation(libs.junit)
  65 + androidTestImplementation(libs.androidx.junit)
  66 + androidTestImplementation(libs.androidx.espresso.core)
  67 + androidTestImplementation(platform(libs.androidx.compose.bom))
  68 + androidTestImplementation(libs.androidx.ui.test.junit4)
  69 + debugImplementation(libs.androidx.ui.tooling)
  70 + debugImplementation(libs.androidx.ui.test.manifest)
  71 +}
  1 +# Add project specific ProGuard rules here.
  2 +# You can control the set of applied configuration files using the
  3 +# proguardFiles setting in build.gradle.
  4 +#
  5 +# For more details, see
  6 +# http://developer.android.com/guide/developing/tools/proguard.html
  7 +
  8 +# If your project uses WebView with JS, uncomment the following
  9 +# and specify the fully qualified class name to the JavaScript interface
  10 +# class:
  11 +#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
  12 +# public *;
  13 +#}
  14 +
  15 +# Uncomment this to preserve the line number information for
  16 +# debugging stack traces.
  17 +#-keepattributes SourceFile,LineNumberTable
  18 +
  19 +# If you keep the line number information, uncomment this to
  20 +# hide the original source file name.
  21 +#-renamesourcefileattribute SourceFile
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import androidx.test.platform.app.InstrumentationRegistry
  4 +import androidx.test.ext.junit.runners.AndroidJUnit4
  5 +
  6 +import org.junit.Test
  7 +import org.junit.runner.RunWith
  8 +
  9 +import org.junit.Assert.*
  10 +
  11 +/**
  12 + * Instrumented test, which will execute on an Android device.
  13 + *
  14 + * See [testing documentation](http://d.android.com/tools/testing).
  15 + */
  16 +@RunWith(AndroidJUnit4::class)
  17 +class ExampleInstrumentedTest {
  18 + @Test
  19 + fun useAppContext() {
  20 + // Context of the app under test.
  21 + val appContext = InstrumentationRegistry.getInstrumentation().targetContext
  22 + assertEquals("com.k2fsa.sherpa.onnx.speaker.diarization", appContext.packageName)
  23 + }
  24 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools">
  4 +
  5 + <uses-permission
  6 + android:name="android.permission.READ_EXTERNAL_STORAGE"
  7 + android:maxSdkVersion="32" />
  8 +
  9 + <application
  10 + android:allowBackup="true"
  11 + android:dataExtractionRules="@xml/data_extraction_rules"
  12 + android:fullBackupContent="@xml/backup_rules"
  13 + android:icon="@mipmap/ic_launcher"
  14 + android:label="@string/app_name"
  15 + android:roundIcon="@mipmap/ic_launcher_round"
  16 + android:supportsRtl="true"
  17 + android:theme="@style/Theme.SherpaOnnxSpeakerDiarization"
  18 + tools:targetApi="31">
  19 + <activity
  20 + android:name=".MainActivity"
  21 + android:exported="true"
  22 + android:label="@string/app_name"
  23 + android:theme="@style/Theme.SherpaOnnxSpeakerDiarization">
  24 + <intent-filter>
  25 + <action android:name="android.intent.action.MAIN" />
  26 +
  27 + <category android:name="android.intent.category.LAUNCHER" />
  28 + </intent-filter>
  29 + </activity>
  30 + </application>
  31 +
  32 +</manifest>
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import androidx.compose.ui.graphics.vector.ImageVector
  4 +
  5 +data class BarItem(
  6 + val title: String,
  7 +
  8 + // see https://www.composables.com/icons
  9 + // and
  10 + // https://developer.android.com/reference/kotlin/androidx/compose/material/icons/filled/package-summary
  11 + val image: ImageVector,
  12 + val route: String,
  13 +)
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import android.os.Bundle
  4 +import androidx.activity.ComponentActivity
  5 +import androidx.activity.compose.setContent
  6 +import androidx.activity.enableEdgeToEdge
  7 +import androidx.compose.foundation.layout.Column
  8 +import androidx.compose.foundation.layout.fillMaxSize
  9 +import androidx.compose.foundation.layout.padding
  10 +import androidx.compose.material3.CenterAlignedTopAppBar
  11 +import androidx.compose.material3.ExperimentalMaterial3Api
  12 +import androidx.compose.material3.Icon
  13 +import androidx.compose.material3.MaterialTheme
  14 +import androidx.compose.material3.NavigationBar
  15 +import androidx.compose.material3.NavigationBarItem
  16 +import androidx.compose.material3.Scaffold
  17 +import androidx.compose.material3.Surface
  18 +import androidx.compose.material3.Text
  19 +import androidx.compose.material3.TopAppBarDefaults
  20 +import androidx.compose.runtime.Composable
  21 +import androidx.compose.runtime.getValue
  22 +import androidx.compose.ui.Modifier
  23 +import androidx.compose.ui.text.font.FontWeight
  24 +import androidx.compose.ui.tooling.preview.Preview
  25 +import androidx.navigation.NavGraph.Companion.findStartDestination
  26 +import androidx.navigation.NavHostController
  27 +import androidx.navigation.compose.NavHost
  28 +import androidx.navigation.compose.composable
  29 +import androidx.navigation.compose.currentBackStackEntryAsState
  30 +import androidx.navigation.compose.rememberNavController
  31 +import com.k2fsa.sherpa.onnx.speaker.diarization.screens.HelpScreen
  32 +import com.k2fsa.sherpa.onnx.speaker.diarization.screens.HomeScreen
  33 +import com.k2fsa.sherpa.onnx.speaker.diarization.ui.theme.SherpaOnnxSpeakerDiarizationTheme
  34 +
  35 +const val TAG = "sherpa-onnx-sd"
  36 +
  37 +class MainActivity : ComponentActivity() {
  38 + override fun onCreate(savedInstanceState: Bundle?) {
  39 + super.onCreate(savedInstanceState)
  40 + enableEdgeToEdge()
  41 + setContent {
  42 + SherpaOnnxSpeakerDiarizationTheme {
  43 + // A surface container using the 'background' color from the theme
  44 + Surface(
  45 + modifier = Modifier.fillMaxSize(),
  46 + color = MaterialTheme.colorScheme.background
  47 + ) {
  48 + MainScreen()
  49 + }
  50 + }
  51 + }
  52 + SpeakerDiarizationObject.initSpeakerDiarization(this.assets)
  53 + }
  54 +}
  55 +
  56 +@OptIn(ExperimentalMaterial3Api::class)
  57 +@Composable
  58 +fun MainScreen(modifier: Modifier = Modifier) {
  59 + val navController = rememberNavController()
  60 + Scaffold(
  61 + topBar = {
  62 + CenterAlignedTopAppBar(
  63 + colors = TopAppBarDefaults.topAppBarColors(
  64 + containerColor = MaterialTheme.colorScheme.primaryContainer,
  65 + titleContentColor = MaterialTheme.colorScheme.primary,
  66 + ),
  67 + title = {
  68 + Text(
  69 + "Next-gen Kaldi: Speaker Diarization",
  70 + fontWeight = FontWeight.Bold,
  71 + )
  72 + },
  73 + )
  74 + },
  75 + content = { padding ->
  76 + Column(Modifier.padding(padding)) {
  77 + NavigationHost(navController = navController)
  78 +
  79 + }
  80 + },
  81 + bottomBar = {
  82 + BottomNavigationBar(navController = navController)
  83 + }
  84 + )
  85 +}
  86 +
  87 +@Composable
  88 +fun NavigationHost(navController: NavHostController) {
  89 + NavHost(navController = navController, startDestination = NavRoutes.Home.route) {
  90 + composable(NavRoutes.Home.route) {
  91 + HomeScreen()
  92 + }
  93 +
  94 + composable(NavRoutes.Help.route) {
  95 + HelpScreen()
  96 + }
  97 + }
  98 +}
  99 +
  100 +@Composable
  101 +fun BottomNavigationBar(navController: NavHostController) {
  102 + NavigationBar {
  103 + val backStackEntry by navController.currentBackStackEntryAsState()
  104 + val currentRoute = backStackEntry?.destination?.route
  105 +
  106 + NavBarItems.BarItems.forEach { navItem ->
  107 + NavigationBarItem(selected = currentRoute == navItem.route,
  108 + onClick = {
  109 + navController.navigate(navItem.route) {
  110 + popUpTo(navController.graph.findStartDestination().id) {
  111 + saveState = true
  112 + }
  113 + launchSingleTop = true
  114 + restoreState = true
  115 + }
  116 + },
  117 + icon = {
  118 + Icon(imageVector = navItem.image, contentDescription = navItem.title)
  119 + }, label = {
  120 + Text(text = navItem.title)
  121 + })
  122 + }
  123 + }
  124 +}
  125 +
  126 +@Preview(showBackground = true)
  127 +@Composable
  128 +fun MainScreenPreview() {
  129 + SherpaOnnxSpeakerDiarizationTheme {
  130 + MainScreen()
  131 + }
  132 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import androidx.compose.material.icons.Icons
  4 +import androidx.compose.material.icons.filled.Home
  5 +import androidx.compose.material.icons.filled.Info
  6 +
  7 +object NavBarItems {
  8 + val BarItems = listOf(
  9 + BarItem(
  10 + title = "Home",
  11 + image = Icons.Filled.Home,
  12 + route = "home",
  13 + ),
  14 + BarItem(
  15 + title = "Help",
  16 + image = Icons.Filled.Info,
  17 + route = "help",
  18 + ),
  19 + )
  20 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +sealed class NavRoutes(val route: String) {
  4 + object Home : NavRoutes("home")
  5 + object Help : NavRoutes("help")
  6 +}
  1 +../../../../../../../../../../../../sherpa-onnx/kotlin-api/OfflineSpeakerDiarization.kt
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.screens
  2 +
  3 +import android.content.Context
  4 +import android.media.AudioFormat
  5 +import android.media.MediaCodec
  6 +import android.media.MediaExtractor
  7 +import android.media.MediaFormat
  8 +import android.net.Uri
  9 +
  10 +data class WaveData(
  11 + val sampleRate: Int? = null,
  12 + val samples: FloatArray? = null,
  13 + val msg: String? = null
  14 +)
  15 +
  16 +// It supports only 16-bit encoded wave files
  17 +//
  18 +// References
  19 +// - https://gist.github.com/a-m-s/1991ab18fbcb0fcc2cf9
  20 +// - https://github.com/taehwandev/MediaCodecExample/blob/master/app/src/main/java/tech/thdev/mediacodecexample/audio/AACAudioDecoderThread.kt
  21 +fun readUri(context: Context, uri: Uri): WaveData {
  22 + val extractor = MediaExtractor()
  23 + extractor.setDataSource(context, uri, null)
  24 +
  25 + val samplesList: MutableList<FloatArray> = ArrayList()
  26 +
  27 + for (i in 0 until extractor.trackCount) {
  28 + val format = extractor.getTrackFormat(i)
  29 + val mime = format.getString(MediaFormat.KEY_MIME)
  30 + if (mime?.startsWith("audio/") == true) {
  31 + extractor.selectTrack(i)
  32 +
  33 + var encoding: Int = -1
  34 + try {
  35 + encoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING)
  36 + } catch (_: Exception) {
  37 + }
  38 +
  39 + if (encoding != AudioFormat.ENCODING_PCM_16BIT) {
  40 + return WaveData(msg = "We support only 16-bit encoded wave files")
  41 + }
  42 +
  43 + val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
  44 + val decoder = MediaCodec.createDecoderByType(mime)
  45 + decoder.configure(format, null, null, 0)
  46 + decoder.start()
  47 +
  48 + val inputBuffers = decoder.inputBuffers
  49 + var outputBuffers = decoder.outputBuffers
  50 +
  51 + val info = MediaCodec.BufferInfo()
  52 + var eof = false
  53 +
  54 + var outputBufferIndex = -1
  55 +
  56 + while (true) {
  57 + if (!eof) {
  58 + val inputBufferIndex = decoder.dequeueInputBuffer(10000)
  59 + if (inputBufferIndex > 0) {
  60 + val size = extractor.readSampleData(inputBuffers[inputBufferIndex], 0)
  61 + if (size < 0) {
  62 + decoder.queueInputBuffer(
  63 + inputBufferIndex,
  64 + 0,
  65 + 0,
  66 + 0,
  67 + MediaCodec.BUFFER_FLAG_END_OF_STREAM
  68 + )
  69 + eof = true
  70 + } else {
  71 + decoder.queueInputBuffer(
  72 + inputBufferIndex,
  73 + 0,
  74 + size,
  75 + extractor.sampleTime,
  76 + 0
  77 + )
  78 + extractor.advance()
  79 + }
  80 + }
  81 + } // if (!eof)
  82 +
  83 + if (outputBufferIndex >= 0) {
  84 + outputBuffers[outputBufferIndex].position(0)
  85 + }
  86 +
  87 + outputBufferIndex = decoder.dequeueOutputBuffer(info, 10000)
  88 + if (outputBufferIndex >= 0) {
  89 + if (info.flags != 0) {
  90 + decoder.stop()
  91 + decoder.release()
  92 +
  93 + var k = 0
  94 + for (s in samplesList) {
  95 + k += s.size
  96 + }
  97 + if (k == 0) {
  98 + return WaveData(msg = "Failed to read selected file")
  99 + }
  100 +
  101 + val ans = FloatArray(k)
  102 + k = 0
  103 + for (s in samplesList) {
  104 + s.copyInto(ans, k)
  105 + k += s.size
  106 + }
  107 +
  108 + return WaveData(sampleRate = sampleRate, samples = ans)
  109 + }
  110 +
  111 + val buffer = outputBuffers[outputBufferIndex]
  112 + val chunk = ByteArray(info.size)
  113 + buffer[chunk]
  114 + buffer.clear()
  115 +
  116 + val numSamples = info.size / 2
  117 +
  118 + val samples = FloatArray(numSamples)
  119 + for (k in 0 until numSamples) {
  120 + // assume little endian
  121 + val s = chunk[2 * k] + (chunk[2 * k + 1] * 256.0f)
  122 +
  123 + samples[k] = s / 32768.0f
  124 + }
  125 + samplesList.add(samples)
  126 +
  127 + decoder.releaseOutputBuffer(outputBufferIndex, false)
  128 + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
  129 + outputBuffers = decoder.outputBuffers
  130 + }
  131 + }
  132 + }
  133 + }
  134 +
  135 + extractor.release()
  136 + return WaveData(msg = "not an audio file")
  137 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import android.content.res.AssetManager
  4 +import android.util.Log
  5 +import com.k2fsa.sherpa.onnx.FastClusteringConfig
  6 +import com.k2fsa.sherpa.onnx.OfflineSpeakerDiarization
  7 +import com.k2fsa.sherpa.onnx.OfflineSpeakerDiarizationConfig
  8 +import com.k2fsa.sherpa.onnx.OfflineSpeakerSegmentationModelConfig
  9 +import com.k2fsa.sherpa.onnx.OfflineSpeakerSegmentationPyannoteModelConfig
  10 +import com.k2fsa.sherpa.onnx.SpeakerEmbeddingExtractorConfig
  11 +
  12 +// Please download
  13 +// https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-segmentation-models/sherpa-onnx-pyannote-segmentation-3-0.tar.bz2
  14 +// then unzip it, rename model.onnx to segmentation.onnx, and mv
  15 +// segmentation.onnx to the assets folder
  16 +val segmentationModel = "segmentation.onnx"
  17 +
  18 +// please download it from
  19 +// https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx
  20 +// and move it to the assets folder
  21 +val embeddingModel = "3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx"
  22 +
  23 +// in the end, your assets folder should look like below
  24 +/*
  25 +(py38) fangjuns-MacBook-Pro:assets fangjun$ pwd
  26 +/Users/fangjun/open-source/sherpa-onnx/android/SherpaOnnxSpeakerDiarization/app/src/main/assets
  27 +(py38) fangjuns-MacBook-Pro:assets fangjun$ ls -lh
  28 +total 89048
  29 +-rw-r--r-- 1 fangjun staff 38M Oct 12 20:28 3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx
  30 +-rw-r--r-- 1 fangjun staff 5.7M Oct 12 20:28 segmentation.onnx
  31 + */
  32 +
  33 +object SpeakerDiarizationObject {
  34 + var _sd: OfflineSpeakerDiarization? = null
  35 + val sd: OfflineSpeakerDiarization
  36 + get() {
  37 + return _sd!!
  38 + }
  39 +
  40 + fun initSpeakerDiarization(assetManager: AssetManager? = null) {
  41 + synchronized(this) {
  42 + if (_sd != null) {
  43 + return
  44 + }
  45 + Log.i(TAG, "Initializing sherpa-onnx speaker diarization")
  46 +
  47 + val config = OfflineSpeakerDiarizationConfig(
  48 + segmentation = OfflineSpeakerSegmentationModelConfig(
  49 + pyannote = OfflineSpeakerSegmentationPyannoteModelConfig(
  50 + segmentationModel
  51 + ),
  52 + debug = true,
  53 + ),
  54 + embedding = SpeakerEmbeddingExtractorConfig(
  55 + model = embeddingModel,
  56 + debug = true,
  57 + numThreads = 2,
  58 + ),
  59 + clustering = FastClusteringConfig(numClusters = -1, threshold = 0.5f),
  60 + minDurationOn = 0.2f,
  61 + minDurationOff = 0.5f,
  62 + )
  63 + _sd = OfflineSpeakerDiarization(assetManager = assetManager, config = config)
  64 + }
  65 + }
  66 +}
  1 +../../../../../../../../../../../../sherpa-onnx/kotlin-api/SpeakerEmbeddingExtractorConfig.kt
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.screens
  2 +
  3 +import androidx.compose.foundation.layout.Box
  4 +import androidx.compose.foundation.layout.Column
  5 +import androidx.compose.foundation.layout.Spacer
  6 +import androidx.compose.foundation.layout.fillMaxSize
  7 +import androidx.compose.foundation.layout.height
  8 +import androidx.compose.foundation.layout.padding
  9 +import androidx.compose.material3.Text
  10 +import androidx.compose.runtime.Composable
  11 +import androidx.compose.ui.Modifier
  12 +import androidx.compose.ui.unit.dp
  13 +import androidx.compose.ui.unit.sp
  14 +
  15 +@Composable
  16 +fun HelpScreen() {
  17 + Box(modifier = Modifier.fillMaxSize()) {
  18 + Column(
  19 + modifier = Modifier.padding(8.dp)
  20 + ) {
  21 + Text(
  22 + "This app accepts only 16kHz 16-bit 1-channel *.wav files. " +
  23 + "It has two arguments: Number of speakers and clustering threshold. " +
  24 + "If you know the actual number of speakers in the file, please set it. " +
  25 + "Otherwise, please set it to 0. In that case, you have to set the threshold. " +
  26 + "A larger threshold leads to fewer segmented speakers."
  27 + )
  28 + Spacer(modifier = Modifier.height(5.dp))
  29 + Text("The speaker segmentation model is from " +
  30 + "pyannote-audio (https://huggingface.co/pyannote/segmentation-3.0), "+
  31 + "whereas the embedding extractor model is from 3D-Speaker (https://github.com/modelscope/3D-Speaker)")
  32 + Spacer(modifier = Modifier.height(5.dp))
  33 + Text("Please see http://github.com/k2-fsa/sherpa-onnx ")
  34 + Spacer(modifier = Modifier.height(5.dp))
  35 + Text("Everything is open-sourced!", fontSize = 20.sp)
  36 + }
  37 + }
  38 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.screens
  2 +
  3 +import android.util.Log
  4 +import androidx.activity.compose.rememberLauncherForActivityResult
  5 +import androidx.activity.result.contract.ActivityResultContracts
  6 +import androidx.compose.foundation.layout.Arrangement
  7 +import androidx.compose.foundation.layout.Column
  8 +import androidx.compose.foundation.layout.Row
  9 +import androidx.compose.foundation.layout.Spacer
  10 +import androidx.compose.foundation.layout.fillMaxWidth
  11 +import androidx.compose.foundation.layout.padding
  12 +import androidx.compose.foundation.layout.size
  13 +import androidx.compose.foundation.rememberScrollState
  14 +import androidx.compose.foundation.verticalScroll
  15 +import androidx.compose.material3.Button
  16 +import androidx.compose.material3.OutlinedTextField
  17 +import androidx.compose.material3.Text
  18 +import androidx.compose.runtime.Composable
  19 +import androidx.compose.runtime.getValue
  20 +import androidx.compose.runtime.mutableStateOf
  21 +import androidx.compose.runtime.remember
  22 +import androidx.compose.runtime.setValue
  23 +import androidx.compose.ui.Alignment
  24 +import androidx.compose.ui.Modifier
  25 +import androidx.compose.ui.platform.LocalClipboardManager
  26 +import androidx.compose.ui.platform.LocalContext
  27 +import androidx.compose.ui.text.AnnotatedString
  28 +import androidx.compose.ui.unit.dp
  29 +import androidx.compose.ui.unit.sp
  30 +import androidx.documentfile.provider.DocumentFile
  31 +import com.k2fsa.sherpa.onnx.speaker.diarization.SpeakerDiarizationObject
  32 +import com.k2fsa.sherpa.onnx.speaker.diarization.TAG
  33 +import kotlin.concurrent.thread
  34 +
  35 +
  36 +private var samples: FloatArray? = null
  37 +
  38 +@Composable
  39 +fun HomeScreen() {
  40 + val context = LocalContext.current
  41 +
  42 + var sampleRate: Int
  43 + var filename by remember { mutableStateOf("") }
  44 + var status by remember { mutableStateOf("") }
  45 + var progress by remember { mutableStateOf("") }
  46 + val clipboardManager = LocalClipboardManager.current
  47 + var done by remember { mutableStateOf(false) }
  48 + var fileIsOk by remember { mutableStateOf(false) }
  49 + var started by remember { mutableStateOf(false) }
  50 + var numSpeakers by remember { mutableStateOf(0) }
  51 + var threshold by remember { mutableStateOf(0.5f) }
  52 +
  53 +
  54 + val callback = here@{ numProcessedChunks: Int, numTotalChunks: Int, arg: Long ->
  55 + Int
  56 + val percent = 100.0 * numProcessedChunks / numTotalChunks
  57 + progress = "%.2f%%".format(percent)
  58 + Log.i(TAG, progress)
  59 + return@here 0
  60 + }
  61 +
  62 + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
  63 + it?.let {
  64 + val documentFile = DocumentFile.fromSingleUri(context, it)
  65 + filename = documentFile?.name ?: ""
  66 +
  67 + progress = ""
  68 + done = false
  69 + fileIsOk = false
  70 +
  71 + if (filename.isNotEmpty()) {
  72 + val data = readUri(context, it)
  73 + Log.i(TAG, "sample rate: ${data.sampleRate}")
  74 + Log.i(TAG, "numSamples: ${data.samples?.size ?: 0}")
  75 + if (data.msg != null) {
  76 + Log.i(TAG, "failed to read $filename")
  77 + status = data.msg
  78 + } else if (data.sampleRate != SpeakerDiarizationObject.sd.sampleRate()) {
  79 + status =
  80 + "Expected sample rate: ${SpeakerDiarizationObject.sd.sampleRate()}. Given wave file with sample rate: ${data.sampleRate}"
  81 + } else {
  82 + samples = data.samples!!
  83 + fileIsOk = true
  84 + }
  85 + }
  86 + }
  87 + }
  88 +
  89 + Column(
  90 + modifier = Modifier.padding(10.dp),
  91 + verticalArrangement = Arrangement.Top,
  92 + ) {
  93 + Row(
  94 + modifier = Modifier.fillMaxWidth(),
  95 + horizontalArrangement = Arrangement.SpaceEvenly,
  96 + verticalAlignment = Alignment.CenterVertically
  97 + ) {
  98 +
  99 + Button(onClick = {
  100 + launcher.launch(arrayOf("audio/*"))
  101 + }) {
  102 + Text("Select a .wav file")
  103 + }
  104 +
  105 + Button(enabled = fileIsOk && !started,
  106 + onClick = {
  107 + Log.i(TAG, "started")
  108 + Log.i(TAG, "num samples: ${samples?.size}")
  109 + started = true
  110 + progress = ""
  111 +
  112 + val config = SpeakerDiarizationObject.sd.config
  113 + config.clustering.numClusters = numSpeakers
  114 + config.clustering.threshold = threshold
  115 +
  116 + SpeakerDiarizationObject.sd.setConfig(config)
  117 +
  118 + thread(true) {
  119 + done = false
  120 + status = "Started! Please wait"
  121 + val segments = SpeakerDiarizationObject.sd.processWithCallback(
  122 + samples!!,
  123 + callback = callback,
  124 + )
  125 + done = true
  126 + started = false
  127 + status = ""
  128 + for (s in segments) {
  129 + val start = "%.2f".format(s.start)
  130 + val end = "%.2f".format(s.end)
  131 + val speaker = "speaker_%02d".format(s.speaker)
  132 + status += "$start -- $end $speaker\n"
  133 + Log.i(TAG, "$start -- $end $speaker")
  134 + }
  135 +
  136 + Log.i(TAG, status)
  137 + }
  138 + }) {
  139 + Text("Start")
  140 + }
  141 + if (progress.isNotEmpty()) {
  142 + Text(progress, fontSize = 25.sp)
  143 + }
  144 + }
  145 +
  146 + Row(
  147 + modifier = Modifier.fillMaxWidth(),
  148 + horizontalArrangement = Arrangement.SpaceEvenly,
  149 + verticalAlignment = Alignment.CenterVertically
  150 + ) {
  151 + OutlinedTextField(
  152 + value = numSpeakers.toString(),
  153 + onValueChange = {
  154 + if (it.isEmpty() || it.isBlank()) {
  155 + numSpeakers = 0
  156 + } else {
  157 + numSpeakers = it.toIntOrNull() ?: 0
  158 + }
  159 + },
  160 + label = {
  161 + Text("Number of Speakers")
  162 + },
  163 + )
  164 + }
  165 +
  166 + Row(
  167 + modifier = Modifier.fillMaxWidth(),
  168 + horizontalArrangement = Arrangement.SpaceEvenly,
  169 + verticalAlignment = Alignment.CenterVertically
  170 + ) {
  171 + OutlinedTextField(
  172 + value = threshold.toString(),
  173 + onValueChange = {
  174 + if (it.isEmpty() || it.isBlank()) {
  175 + threshold = 0.5f
  176 + } else {
  177 + threshold = it.toFloatOrNull() ?: 0.5f
  178 + }
  179 + },
  180 + label = {
  181 + Text("Clustering threshold")
  182 + },
  183 + )
  184 + }
  185 +
  186 + if (filename.isNotEmpty()) {
  187 + Text(text = "Selected $filename")
  188 + Spacer(Modifier.size(20.dp))
  189 + }
  190 +
  191 + if (done) {
  192 + Button(onClick = {
  193 + clipboardManager.setText(AnnotatedString(status))
  194 + progress = "Copied!"
  195 + }) {
  196 + Text("Copy result")
  197 + }
  198 + Spacer(Modifier.size(20.dp))
  199 + }
  200 +
  201 + if (status.isNotEmpty()) {
  202 + Text(
  203 + status,
  204 + modifier = Modifier.verticalScroll(rememberScrollState()),
  205 + )
  206 + }
  207 +
  208 +
  209 + }
  210 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.ui.theme
  2 +
  3 +import androidx.compose.ui.graphics.Color
  4 +
  5 +val Purple80 = Color(0xFFD0BCFF)
  6 +val PurpleGrey80 = Color(0xFFCCC2DC)
  7 +val Pink80 = Color(0xFFEFB8C8)
  8 +
  9 +val Purple40 = Color(0xFF6650a4)
  10 +val PurpleGrey40 = Color(0xFF625b71)
  11 +val Pink40 = Color(0xFF7D5260)
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.ui.theme
  2 +
  3 +import android.app.Activity
  4 +import android.os.Build
  5 +import androidx.compose.foundation.isSystemInDarkTheme
  6 +import androidx.compose.material3.MaterialTheme
  7 +import androidx.compose.material3.darkColorScheme
  8 +import androidx.compose.material3.dynamicDarkColorScheme
  9 +import androidx.compose.material3.dynamicLightColorScheme
  10 +import androidx.compose.material3.lightColorScheme
  11 +import androidx.compose.runtime.Composable
  12 +import androidx.compose.ui.platform.LocalContext
  13 +
  14 +private val DarkColorScheme = darkColorScheme(
  15 + primary = Purple80,
  16 + secondary = PurpleGrey80,
  17 + tertiary = Pink80
  18 +)
  19 +
  20 +private val LightColorScheme = lightColorScheme(
  21 + primary = Purple40,
  22 + secondary = PurpleGrey40,
  23 + tertiary = Pink40
  24 +
  25 + /* Other default colors to override
  26 + background = Color(0xFFFFFBFE),
  27 + surface = Color(0xFFFFFBFE),
  28 + onPrimary = Color.White,
  29 + onSecondary = Color.White,
  30 + onTertiary = Color.White,
  31 + onBackground = Color(0xFF1C1B1F),
  32 + onSurface = Color(0xFF1C1B1F),
  33 + */
  34 +)
  35 +
  36 +@Composable
  37 +fun SherpaOnnxSpeakerDiarizationTheme(
  38 + darkTheme: Boolean = isSystemInDarkTheme(),
  39 + // Dynamic color is available on Android 12+
  40 + dynamicColor: Boolean = true,
  41 + content: @Composable () -> Unit
  42 +) {
  43 + val colorScheme = when {
  44 + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
  45 + val context = LocalContext.current
  46 + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
  47 + }
  48 +
  49 + darkTheme -> DarkColorScheme
  50 + else -> LightColorScheme
  51 + }
  52 +
  53 + MaterialTheme(
  54 + colorScheme = colorScheme,
  55 + typography = Typography,
  56 + content = content
  57 + )
  58 +}
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization.ui.theme
  2 +
  3 +import androidx.compose.material3.Typography
  4 +import androidx.compose.ui.text.TextStyle
  5 +import androidx.compose.ui.text.font.FontFamily
  6 +import androidx.compose.ui.text.font.FontWeight
  7 +import androidx.compose.ui.unit.sp
  8 +
  9 +// Set of Material typography styles to start with
  10 +val Typography = Typography(
  11 + bodyLarge = TextStyle(
  12 + fontFamily = FontFamily.Default,
  13 + fontWeight = FontWeight.Normal,
  14 + fontSize = 16.sp,
  15 + lineHeight = 24.sp,
  16 + letterSpacing = 0.5.sp
  17 + )
  18 + /* Other default text styles to override
  19 + titleLarge = TextStyle(
  20 + fontFamily = FontFamily.Default,
  21 + fontWeight = FontWeight.Normal,
  22 + fontSize = 22.sp,
  23 + lineHeight = 28.sp,
  24 + letterSpacing = 0.sp
  25 + ),
  26 + labelSmall = TextStyle(
  27 + fontFamily = FontFamily.Default,
  28 + fontWeight = FontWeight.Medium,
  29 + fontSize = 11.sp,
  30 + lineHeight = 16.sp,
  31 + letterSpacing = 0.5.sp
  32 + )
  33 + */
  34 +)
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + xmlns:aapt="http://schemas.android.com/aapt"
  3 + android:width="108dp"
  4 + android:height="108dp"
  5 + android:viewportWidth="108"
  6 + android:viewportHeight="108">
  7 + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
  8 + <aapt:attr name="android:fillColor">
  9 + <gradient
  10 + android:endX="85.84757"
  11 + android:endY="92.4963"
  12 + android:startX="42.9492"
  13 + android:startY="49.59793"
  14 + android:type="linear">
  15 + <item
  16 + android:color="#44000000"
  17 + android:offset="0.0" />
  18 + <item
  19 + android:color="#00000000"
  20 + android:offset="1.0" />
  21 + </gradient>
  22 + </aapt:attr>
  23 + </path>
  24 + <path
  25 + android:fillColor="#FFFFFF"
  26 + android:fillType="nonZero"
  27 + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
  28 + android:strokeWidth="1"
  29 + android:strokeColor="#00000000" />
  30 +</vector>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:width="108dp"
  4 + android:height="108dp"
  5 + android:viewportWidth="108"
  6 + android:viewportHeight="108">
  7 + <path
  8 + android:fillColor="#3DDC84"
  9 + android:pathData="M0,0h108v108h-108z" />
  10 + <path
  11 + android:fillColor="#00000000"
  12 + android:pathData="M9,0L9,108"
  13 + android:strokeWidth="0.8"
  14 + android:strokeColor="#33FFFFFF" />
  15 + <path
  16 + android:fillColor="#00000000"
  17 + android:pathData="M19,0L19,108"
  18 + android:strokeWidth="0.8"
  19 + android:strokeColor="#33FFFFFF" />
  20 + <path
  21 + android:fillColor="#00000000"
  22 + android:pathData="M29,0L29,108"
  23 + android:strokeWidth="0.8"
  24 + android:strokeColor="#33FFFFFF" />
  25 + <path
  26 + android:fillColor="#00000000"
  27 + android:pathData="M39,0L39,108"
  28 + android:strokeWidth="0.8"
  29 + android:strokeColor="#33FFFFFF" />
  30 + <path
  31 + android:fillColor="#00000000"
  32 + android:pathData="M49,0L49,108"
  33 + android:strokeWidth="0.8"
  34 + android:strokeColor="#33FFFFFF" />
  35 + <path
  36 + android:fillColor="#00000000"
  37 + android:pathData="M59,0L59,108"
  38 + android:strokeWidth="0.8"
  39 + android:strokeColor="#33FFFFFF" />
  40 + <path
  41 + android:fillColor="#00000000"
  42 + android:pathData="M69,0L69,108"
  43 + android:strokeWidth="0.8"
  44 + android:strokeColor="#33FFFFFF" />
  45 + <path
  46 + android:fillColor="#00000000"
  47 + android:pathData="M79,0L79,108"
  48 + android:strokeWidth="0.8"
  49 + android:strokeColor="#33FFFFFF" />
  50 + <path
  51 + android:fillColor="#00000000"
  52 + android:pathData="M89,0L89,108"
  53 + android:strokeWidth="0.8"
  54 + android:strokeColor="#33FFFFFF" />
  55 + <path
  56 + android:fillColor="#00000000"
  57 + android:pathData="M99,0L99,108"
  58 + android:strokeWidth="0.8"
  59 + android:strokeColor="#33FFFFFF" />
  60 + <path
  61 + android:fillColor="#00000000"
  62 + android:pathData="M0,9L108,9"
  63 + android:strokeWidth="0.8"
  64 + android:strokeColor="#33FFFFFF" />
  65 + <path
  66 + android:fillColor="#00000000"
  67 + android:pathData="M0,19L108,19"
  68 + android:strokeWidth="0.8"
  69 + android:strokeColor="#33FFFFFF" />
  70 + <path
  71 + android:fillColor="#00000000"
  72 + android:pathData="M0,29L108,29"
  73 + android:strokeWidth="0.8"
  74 + android:strokeColor="#33FFFFFF" />
  75 + <path
  76 + android:fillColor="#00000000"
  77 + android:pathData="M0,39L108,39"
  78 + android:strokeWidth="0.8"
  79 + android:strokeColor="#33FFFFFF" />
  80 + <path
  81 + android:fillColor="#00000000"
  82 + android:pathData="M0,49L108,49"
  83 + android:strokeWidth="0.8"
  84 + android:strokeColor="#33FFFFFF" />
  85 + <path
  86 + android:fillColor="#00000000"
  87 + android:pathData="M0,59L108,59"
  88 + android:strokeWidth="0.8"
  89 + android:strokeColor="#33FFFFFF" />
  90 + <path
  91 + android:fillColor="#00000000"
  92 + android:pathData="M0,69L108,69"
  93 + android:strokeWidth="0.8"
  94 + android:strokeColor="#33FFFFFF" />
  95 + <path
  96 + android:fillColor="#00000000"
  97 + android:pathData="M0,79L108,79"
  98 + android:strokeWidth="0.8"
  99 + android:strokeColor="#33FFFFFF" />
  100 + <path
  101 + android:fillColor="#00000000"
  102 + android:pathData="M0,89L108,89"
  103 + android:strokeWidth="0.8"
  104 + android:strokeColor="#33FFFFFF" />
  105 + <path
  106 + android:fillColor="#00000000"
  107 + android:pathData="M0,99L108,99"
  108 + android:strokeWidth="0.8"
  109 + android:strokeColor="#33FFFFFF" />
  110 + <path
  111 + android:fillColor="#00000000"
  112 + android:pathData="M19,29L89,29"
  113 + android:strokeWidth="0.8"
  114 + android:strokeColor="#33FFFFFF" />
  115 + <path
  116 + android:fillColor="#00000000"
  117 + android:pathData="M19,39L89,39"
  118 + android:strokeWidth="0.8"
  119 + android:strokeColor="#33FFFFFF" />
  120 + <path
  121 + android:fillColor="#00000000"
  122 + android:pathData="M19,49L89,49"
  123 + android:strokeWidth="0.8"
  124 + android:strokeColor="#33FFFFFF" />
  125 + <path
  126 + android:fillColor="#00000000"
  127 + android:pathData="M19,59L89,59"
  128 + android:strokeWidth="0.8"
  129 + android:strokeColor="#33FFFFFF" />
  130 + <path
  131 + android:fillColor="#00000000"
  132 + android:pathData="M19,69L89,69"
  133 + android:strokeWidth="0.8"
  134 + android:strokeColor="#33FFFFFF" />
  135 + <path
  136 + android:fillColor="#00000000"
  137 + android:pathData="M19,79L89,79"
  138 + android:strokeWidth="0.8"
  139 + android:strokeColor="#33FFFFFF" />
  140 + <path
  141 + android:fillColor="#00000000"
  142 + android:pathData="M29,19L29,89"
  143 + android:strokeWidth="0.8"
  144 + android:strokeColor="#33FFFFFF" />
  145 + <path
  146 + android:fillColor="#00000000"
  147 + android:pathData="M39,19L39,89"
  148 + android:strokeWidth="0.8"
  149 + android:strokeColor="#33FFFFFF" />
  150 + <path
  151 + android:fillColor="#00000000"
  152 + android:pathData="M49,19L49,89"
  153 + android:strokeWidth="0.8"
  154 + android:strokeColor="#33FFFFFF" />
  155 + <path
  156 + android:fillColor="#00000000"
  157 + android:pathData="M59,19L59,89"
  158 + android:strokeWidth="0.8"
  159 + android:strokeColor="#33FFFFFF" />
  160 + <path
  161 + android:fillColor="#00000000"
  162 + android:pathData="M69,19L69,89"
  163 + android:strokeWidth="0.8"
  164 + android:strokeColor="#33FFFFFF" />
  165 + <path
  166 + android:fillColor="#00000000"
  167 + android:pathData="M79,19L79,89"
  168 + android:strokeWidth="0.8"
  169 + android:strokeColor="#33FFFFFF" />
  170 +</vector>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <background android:drawable="@drawable/ic_launcher_background" />
  4 + <foreground android:drawable="@drawable/ic_launcher_foreground" />
  5 + <monochrome android:drawable="@drawable/ic_launcher_foreground" />
  6 +</adaptive-icon>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <background android:drawable="@drawable/ic_launcher_background" />
  4 + <foreground android:drawable="@drawable/ic_launcher_foreground" />
  5 + <monochrome android:drawable="@drawable/ic_launcher_foreground" />
  6 +</adaptive-icon>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <color name="purple_200">#FFBB86FC</color>
  4 + <color name="purple_500">#FF6200EE</color>
  5 + <color name="purple_700">#FF3700B3</color>
  6 + <color name="teal_200">#FF03DAC5</color>
  7 + <color name="teal_700">#FF018786</color>
  8 + <color name="black">#FF000000</color>
  9 + <color name="white">#FFFFFFFF</color>
  10 +</resources>
  1 +<resources>
  2 + <string name="app_name">SherpaOnnxSpeakerDiarization</string>
  3 +</resources>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 +
  4 + <style name="Theme.SherpaOnnxSpeakerDiarization" parent="android:Theme.Material.Light.NoActionBar" />
  5 +</resources>
  1 +<?xml version="1.0" encoding="utf-8"?><!--
  2 + Sample backup rules file; uncomment and customize as necessary.
  3 + See https://developer.android.com/guide/topics/data/autobackup
  4 + for details.
  5 + Note: This file is ignored for devices older that API 31
  6 + See https://developer.android.com/about/versions/12/backup-restore
  7 +-->
  8 +<full-backup-content>
  9 + <!--
  10 + <include domain="sharedpref" path="."/>
  11 + <exclude domain="sharedpref" path="device.xml"/>
  12 +-->
  13 +</full-backup-content>
  1 +<?xml version="1.0" encoding="utf-8"?><!--
  2 + Sample data extraction rules file; uncomment and customize as necessary.
  3 + See https://developer.android.com/about/versions/12/backup-restore#xml-changes
  4 + for details.
  5 +-->
  6 +<data-extraction-rules>
  7 + <cloud-backup>
  8 + <!-- TODO: Use <include> and <exclude> to control what is backed up.
  9 + <include .../>
  10 + <exclude .../>
  11 + -->
  12 + </cloud-backup>
  13 + <!--
  14 + <device-transfer>
  15 + <include .../>
  16 + <exclude .../>
  17 + </device-transfer>
  18 + -->
  19 +</data-extraction-rules>
  1 +package com.k2fsa.sherpa.onnx.speaker.diarization
  2 +
  3 +import org.junit.Test
  4 +
  5 +import org.junit.Assert.*
  6 +
  7 +/**
  8 + * Example local unit test, which will execute on the development machine (host).
  9 + *
  10 + * See [testing documentation](http://d.android.com/tools/testing).
  11 + */
  12 +class ExampleUnitTest {
  13 + @Test
  14 + fun addition_isCorrect() {
  15 + assertEquals(4, 2 + 2)
  16 + }
  17 +}
  1 +// Top-level build file where you can add configuration options common to all sub-projects/modules.
  2 +plugins {
  3 + alias(libs.plugins.android.application) apply false
  4 + alias(libs.plugins.jetbrains.kotlin.android) apply false
  5 +}
  1 +# Project-wide Gradle settings.
  2 +# IDE (e.g. Android Studio) users:
  3 +# Gradle settings configured through the IDE *will override*
  4 +# any settings specified in this file.
  5 +# For more details on how to configure your build environment visit
  6 +# http://www.gradle.org/docs/current/userguide/build_environment.html
  7 +# Specifies the JVM arguments used for the daemon process.
  8 +# The setting is particularly useful for tweaking memory settings.
  9 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
  10 +# When configured, Gradle will run in incubating parallel mode.
  11 +# This option should only be used with decoupled projects. For more details, visit
  12 +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
  13 +# org.gradle.parallel=true
  14 +# AndroidX package structure to make it clearer which packages are bundled with the
  15 +# Android operating system, and which are packaged with your app's APK
  16 +# https://developer.android.com/topic/libraries/support-library/androidx-rn
  17 +android.useAndroidX=true
  18 +# Kotlin code style for this project: "official" or "obsolete":
  19 +kotlin.code.style=official
  20 +# Enables namespacing of each library's R class so that its R class includes only the
  21 +# resources declared in the library itself and none from the library's dependencies,
  22 +# thereby reducing the size of the R class for that library
  23 +android.nonTransitiveRClass=true
  1 +[versions]
  2 +agp = "8.4.0"
  3 +kotlin = "1.9.0"
  4 +coreKtx = "1.10.1"
  5 +junit = "4.13.2"
  6 +junitVersion = "1.1.5"
  7 +espressoCore = "3.5.1"
  8 +lifecycleRuntimeKtx = "2.6.1"
  9 +activityCompose = "1.8.0"
  10 +composeBom = "2023.08.00"
  11 +navigationCompose = "2.8.2"
  12 +documentfile = "1.0.1"
  13 +
  14 +[libraries]
  15 +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
  16 +junit = { group = "junit", name = "junit", version.ref = "junit" }
  17 +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
  18 +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
  19 +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
  20 +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
  21 +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
  22 +androidx-ui = { group = "androidx.compose.ui", name = "ui" }
  23 +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
  24 +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
  25 +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
  26 +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
  27 +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
  28 +androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
  29 +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
  30 +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
  31 +
  32 +[plugins]
  33 +android-application = { id = "com.android.application", version.ref = "agp" }
  34 +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
  35 +
  1 +#Sat Oct 12 14:27:04 CST 2024
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionPath=wrapper/dists
  4 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
  5 +zipStoreBase=GRADLE_USER_HOME
  6 +zipStorePath=wrapper/dists
  1 +#!/usr/bin/env sh
  2 +
  3 +#
  4 +# Copyright 2015 the original author or authors.
  5 +#
  6 +# Licensed under the Apache License, Version 2.0 (the "License");
  7 +# you may not use this file except in compliance with the License.
  8 +# You may obtain a copy of the License at
  9 +#
  10 +# https://www.apache.org/licenses/LICENSE-2.0
  11 +#
  12 +# Unless required by applicable law or agreed to in writing, software
  13 +# distributed under the License is distributed on an "AS IS" BASIS,
  14 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 +# See the License for the specific language governing permissions and
  16 +# limitations under the License.
  17 +#
  18 +
  19 +##############################################################################
  20 +##
  21 +## Gradle start up script for UN*X
  22 +##
  23 +##############################################################################
  24 +
  25 +# Attempt to set APP_HOME
  26 +# Resolve links: $0 may be a link
  27 +PRG="$0"
  28 +# Need this for relative symlinks.
  29 +while [ -h "$PRG" ] ; do
  30 + ls=`ls -ld "$PRG"`
  31 + link=`expr "$ls" : '.*-> \(.*\)$'`
  32 + if expr "$link" : '/.*' > /dev/null; then
  33 + PRG="$link"
  34 + else
  35 + PRG=`dirname "$PRG"`"/$link"
  36 + fi
  37 +done
  38 +SAVED="`pwd`"
  39 +cd "`dirname \"$PRG\"`/" >/dev/null
  40 +APP_HOME="`pwd -P`"
  41 +cd "$SAVED" >/dev/null
  42 +
  43 +APP_NAME="Gradle"
  44 +APP_BASE_NAME=`basename "$0"`
  45 +
  46 +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
  47 +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
  48 +
  49 +# Use the maximum available, or set MAX_FD != -1 to use that value.
  50 +MAX_FD="maximum"
  51 +
  52 +warn () {
  53 + echo "$*"
  54 +}
  55 +
  56 +die () {
  57 + echo
  58 + echo "$*"
  59 + echo
  60 + exit 1
  61 +}
  62 +
  63 +# OS specific support (must be 'true' or 'false').
  64 +cygwin=false
  65 +msys=false
  66 +darwin=false
  67 +nonstop=false
  68 +case "`uname`" in
  69 + CYGWIN* )
  70 + cygwin=true
  71 + ;;
  72 + Darwin* )
  73 + darwin=true
  74 + ;;
  75 + MINGW* )
  76 + msys=true
  77 + ;;
  78 + NONSTOP* )
  79 + nonstop=true
  80 + ;;
  81 +esac
  82 +
  83 +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
  84 +
  85 +
  86 +# Determine the Java command to use to start the JVM.
  87 +if [ -n "$JAVA_HOME" ] ; then
  88 + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
  89 + # IBM's JDK on AIX uses strange locations for the executables
  90 + JAVACMD="$JAVA_HOME/jre/sh/java"
  91 + else
  92 + JAVACMD="$JAVA_HOME/bin/java"
  93 + fi
  94 + if [ ! -x "$JAVACMD" ] ; then
  95 + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
  96 +
  97 +Please set the JAVA_HOME variable in your environment to match the
  98 +location of your Java installation."
  99 + fi
  100 +else
  101 + JAVACMD="java"
  102 + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
  103 +
  104 +Please set the JAVA_HOME variable in your environment to match the
  105 +location of your Java installation."
  106 +fi
  107 +
  108 +# Increase the maximum file descriptors if we can.
  109 +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
  110 + MAX_FD_LIMIT=`ulimit -H -n`
  111 + if [ $? -eq 0 ] ; then
  112 + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
  113 + MAX_FD="$MAX_FD_LIMIT"
  114 + fi
  115 + ulimit -n $MAX_FD
  116 + if [ $? -ne 0 ] ; then
  117 + warn "Could not set maximum file descriptor limit: $MAX_FD"
  118 + fi
  119 + else
  120 + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
  121 + fi
  122 +fi
  123 +
  124 +# For Darwin, add options to specify how the application appears in the dock
  125 +if $darwin; then
  126 + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
  127 +fi
  128 +
  129 +# For Cygwin or MSYS, switch paths to Windows format before running java
  130 +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
  131 + APP_HOME=`cygpath --path --mixed "$APP_HOME"`
  132 + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
  133 +
  134 + JAVACMD=`cygpath --unix "$JAVACMD"`
  135 +
  136 + # We build the pattern for arguments to be converted via cygpath
  137 + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
  138 + SEP=""
  139 + for dir in $ROOTDIRSRAW ; do
  140 + ROOTDIRS="$ROOTDIRS$SEP$dir"
  141 + SEP="|"
  142 + done
  143 + OURCYGPATTERN="(^($ROOTDIRS))"
  144 + # Add a user-defined pattern to the cygpath arguments
  145 + if [ "$GRADLE_CYGPATTERN" != "" ] ; then
  146 + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
  147 + fi
  148 + # Now convert the arguments - kludge to limit ourselves to /bin/sh
  149 + i=0
  150 + for arg in "$@" ; do
  151 + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
  152 + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
  153 +
  154 + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
  155 + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
  156 + else
  157 + eval `echo args$i`="\"$arg\""
  158 + fi
  159 + i=`expr $i + 1`
  160 + done
  161 + case $i in
  162 + 0) set -- ;;
  163 + 1) set -- "$args0" ;;
  164 + 2) set -- "$args0" "$args1" ;;
  165 + 3) set -- "$args0" "$args1" "$args2" ;;
  166 + 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
  167 + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
  168 + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
  169 + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
  170 + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
  171 + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
  172 + esac
  173 +fi
  174 +
  175 +# Escape application args
  176 +save () {
  177 + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
  178 + echo " "
  179 +}
  180 +APP_ARGS=`save "$@"`
  181 +
  182 +# Collect all arguments for the java command, following the shell quoting and substitution rules
  183 +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
  184 +
  185 +exec "$JAVACMD" "$@"
  1 +@rem
  2 +@rem Copyright 2015 the original author or authors.
  3 +@rem
  4 +@rem Licensed under the Apache License, Version 2.0 (the "License");
  5 +@rem you may not use this file except in compliance with the License.
  6 +@rem You may obtain a copy of the License at
  7 +@rem
  8 +@rem https://www.apache.org/licenses/LICENSE-2.0
  9 +@rem
  10 +@rem Unless required by applicable law or agreed to in writing, software
  11 +@rem distributed under the License is distributed on an "AS IS" BASIS,
  12 +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +@rem See the License for the specific language governing permissions and
  14 +@rem limitations under the License.
  15 +@rem
  16 +
  17 +@if "%DEBUG%" == "" @echo off
  18 +@rem ##########################################################################
  19 +@rem
  20 +@rem Gradle startup script for Windows
  21 +@rem
  22 +@rem ##########################################################################
  23 +
  24 +@rem Set local scope for the variables with windows NT shell
  25 +if "%OS%"=="Windows_NT" setlocal
  26 +
  27 +set DIRNAME=%~dp0
  28 +if "%DIRNAME%" == "" set DIRNAME=.
  29 +set APP_BASE_NAME=%~n0
  30 +set APP_HOME=%DIRNAME%
  31 +
  32 +@rem Resolve any "." and ".." in APP_HOME to make it shorter.
  33 +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
  34 +
  35 +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
  36 +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
  37 +
  38 +@rem Find java.exe
  39 +if defined JAVA_HOME goto findJavaFromJavaHome
  40 +
  41 +set JAVA_EXE=java.exe
  42 +%JAVA_EXE% -version >NUL 2>&1
  43 +if "%ERRORLEVEL%" == "0" goto execute
  44 +
  45 +echo.
  46 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
  47 +echo.
  48 +echo Please set the JAVA_HOME variable in your environment to match the
  49 +echo location of your Java installation.
  50 +
  51 +goto fail
  52 +
  53 +:findJavaFromJavaHome
  54 +set JAVA_HOME=%JAVA_HOME:"=%
  55 +set JAVA_EXE=%JAVA_HOME%/bin/java.exe
  56 +
  57 +if exist "%JAVA_EXE%" goto execute
  58 +
  59 +echo.
  60 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
  61 +echo.
  62 +echo Please set the JAVA_HOME variable in your environment to match the
  63 +echo location of your Java installation.
  64 +
  65 +goto fail
  66 +
  67 +:execute
  68 +@rem Setup the command line
  69 +
  70 +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
  71 +
  72 +
  73 +@rem Execute Gradle
  74 +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
  75 +
  76 +:end
  77 +@rem End local scope for the variables with windows NT shell
  78 +if "%ERRORLEVEL%"=="0" goto mainEnd
  79 +
  80 +:fail
  81 +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
  82 +rem the _cmd.exe /c_ return code!
  83 +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
  84 +exit /b 1
  85 +
  86 +:mainEnd
  87 +if "%OS%"=="Windows_NT" endlocal
  88 +
  89 +:omega
  1 +pluginManagement {
  2 + repositories {
  3 + google {
  4 + content {
  5 + includeGroupByRegex("com\\.android.*")
  6 + includeGroupByRegex("com\\.google.*")
  7 + includeGroupByRegex("androidx.*")
  8 + }
  9 + }
  10 + mavenCentral()
  11 + gradlePluginPortal()
  12 + }
  13 +}
  14 +dependencyResolutionManagement {
  15 + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  16 + repositories {
  17 + google()
  18 + mavenCentral()
  19 + }
  20 +}
  21 +
  22 +rootProject.name = "SherpaOnnxSpeakerDiarization"
  23 +include(":app")
  1 +../../../../../../../../../../../../sherpa-onnx/kotlin-api/SpeakerEmbeddingExtractorConfig.kt
  1 +../sherpa-onnx/kotlin-api/SpeakerEmbeddingExtractorConfig.kt
@@ -48,6 +48,7 @@ function testSpeakerEmbeddingExtractor() { @@ -48,6 +48,7 @@ function testSpeakerEmbeddingExtractor() {
48 test_speaker_id.kt \ 48 test_speaker_id.kt \
49 OnlineStream.kt \ 49 OnlineStream.kt \
50 Speaker.kt \ 50 Speaker.kt \
  51 + SpeakerEmbeddingExtractorConfig.kt \
51 WaveReader.kt \ 52 WaveReader.kt \
52 faked-asset-manager.kt \ 53 faked-asset-manager.kt \
53 faked-log.kt 54 faked-log.kt
@@ -305,6 +306,7 @@ function testOfflineSpeakerDiarization() { @@ -305,6 +306,7 @@ function testOfflineSpeakerDiarization() {
305 test_offline_speaker_diarization.kt \ 306 test_offline_speaker_diarization.kt \
306 OfflineSpeakerDiarization.kt \ 307 OfflineSpeakerDiarization.kt \
307 Speaker.kt \ 308 Speaker.kt \
  309 + SpeakerEmbeddingExtractorConfig.kt \
308 OnlineStream.kt \ 310 OnlineStream.kt \
309 WaveReader.kt \ 311 WaveReader.kt \
310 faked-asset-manager.kt \ 312 faked-asset-manager.kt \
  1 +#!/usr/bin/env bash
  2 +#
  3 +# Please set the environment variable ANDROID_NDK
  4 +# before running this script
  5 +
  6 +# Inside the $ANDROID_NDK directory, you can find a binary ndk-build
  7 +# and some other files like the file "build/cmake/android.toolchain.cmake"
  8 +
  9 +set -ex
  10 +
  11 +log() {
  12 + # This function is from espnet
  13 + local fname=${BASH_SOURCE[1]##*/}
  14 + echo -e "$(date '+%Y-%m-%d %H:%M:%S') (${fname}:${BASH_LINENO[0]}:${FUNCNAME[1]}) $*"
  15 +}
  16 +
  17 +SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  18 +
  19 +log "Building Speaker identification APK for sherpa-onnx v${SHERPA_ONNX_VERSION}"
  20 +
  21 +export SHERPA_ONNX_ENABLE_TTS=OFF
  22 +
  23 +log "====================arm64-v8a================="
  24 +./build-android-arm64-v8a.sh
  25 +log "====================armv7-eabi================"
  26 +./build-android-armv7-eabi.sh
  27 +log "====================x86-64===================="
  28 +./build-android-x86-64.sh
  29 +log "====================x86===================="
  30 +./build-android-x86.sh
  31 +
  32 +mkdir -p apks
  33 +
  34 +pushd ./android/SherpaOnnxSpeakerDiarization/app/src/main/assets/
  35 +
  36 +curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-segmentation-models/sherpa-onnx-pyannote-segmentation-3-0.tar.bz2
  37 +tar xvf sherpa-onnx-pyannote-segmentation-3-0.tar.bz2
  38 +rm sherpa-onnx-pyannote-segmentation-3-0.tar.bz2
  39 +mv sherpa-onnx-pyannote-segmentation-3-0/model.onnx segmentation.onnx
  40 +rm -rf sherpa-onnx-pyannote-segmentation-3-0
  41 +
  42 +curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-recongition-models/3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx
  43 +
  44 +echo "pwd: $PWD"
  45 +ls -lh
  46 +
  47 +popd
  48 +
  49 +for arch in arm64-v8a armeabi-v7a x86_64 x86; do
  50 + log "------------------------------------------------------------"
  51 + log "build speaker diarization apk for $arch"
  52 + log "------------------------------------------------------------"
  53 + src_arch=$arch
  54 + if [ $arch == "armeabi-v7a" ]; then
  55 + src_arch=armv7-eabi
  56 + elif [ $arch == "x86_64" ]; then
  57 + src_arch=x86-64
  58 + fi
  59 +
  60 + ls -lh ./build-android-$src_arch/install/lib/*.so
  61 +
  62 + cp -v ./build-android-$src_arch/install/lib/*.so ./android/SherpaOnnxSpeakerDiarization/app/src/main/jniLibs/$arch/
  63 +
  64 + pushd ./android/SherpaOnnxSpeakerDiarization
  65 + ./gradlew build
  66 + popd
  67 +
  68 + mv android/SherpaOnnxSpeakerDiarization/app/build/outputs/apk/debug/app-debug.apk ./apks/sherpa-onnx-${SHERPA_ONNX_VERSION}-$arch-speaker-diarization-pyannote_audio-3dspeaker.apk
  69 + ls -lh apks
  70 + rm -v ./android/SherpaOnnxSpeakerDiarization/app/src/main/jniLibs/$arch/*.so
  71 +done
  72 +
  73 +ls -lh apks
@@ -34,7 +34,7 @@ data class OfflineSpeakerDiarizationSegment( @@ -34,7 +34,7 @@ data class OfflineSpeakerDiarizationSegment(
34 34
35 class OfflineSpeakerDiarization( 35 class OfflineSpeakerDiarization(
36 assetManager: AssetManager? = null, 36 assetManager: AssetManager? = null,
37 - config: OfflineSpeakerDiarizationConfig, 37 + val config: OfflineSpeakerDiarizationConfig,
38 ) { 38 ) {
39 private var ptr: Long 39 private var ptr: Long
40 40
@@ -84,7 +84,10 @@ class OfflineSpeakerDiarization( @@ -84,7 +84,10 @@ class OfflineSpeakerDiarization(
84 84
85 private external fun getSampleRate(ptr: Long): Int 85 private external fun getSampleRate(ptr: Long): Int
86 86
87 - private external fun process(ptr: Long, samples: FloatArray): Array<OfflineSpeakerDiarizationSegment> 87 + private external fun process(
  88 + ptr: Long,
  89 + samples: FloatArray
  90 + ): Array<OfflineSpeakerDiarizationSegment>
88 91
89 private external fun processWithCallback( 92 private external fun processWithCallback(
90 ptr: Long, 93 ptr: Long,
@@ -3,13 +3,6 @@ package com.k2fsa.sherpa.onnx @@ -3,13 +3,6 @@ package com.k2fsa.sherpa.onnx
3 import android.content.res.AssetManager 3 import android.content.res.AssetManager
4 import android.util.Log 4 import android.util.Log
5 5
6 -data class SpeakerEmbeddingExtractorConfig(  
7 - val model: String,  
8 - var numThreads: Int = 1,  
9 - var debug: Boolean = false,  
10 - var provider: String = "cpu",  
11 -)  
12 -  
13 class SpeakerEmbeddingExtractor( 6 class SpeakerEmbeddingExtractor(
14 assetManager: AssetManager? = null, 7 assetManager: AssetManager? = null,
15 config: SpeakerEmbeddingExtractorConfig, 8 config: SpeakerEmbeddingExtractorConfig,
  1 +package com.k2fsa.sherpa.onnx
  2 +
  3 +data class SpeakerEmbeddingExtractorConfig(
  4 + val model: String,
  5 + var numThreads: Int = 1,
  6 + var debug: Boolean = false,
  7 + var provider: String = "cpu",
  8 +)