Sync from bytedesk-private: update
71
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/build.gradle
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.yeyupiaoling.androidclient'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.yeyupiaoling.androidclient"
|
||||
minSdk 24
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.4.3'
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
|
||||
implementation 'androidx.activity:activity-compose:1.7.0'
|
||||
implementation platform('androidx.compose:compose-bom:2023.03.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00')
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
||||
}
|
||||
21
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yeyupiaoling.androidclient
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.yeyupiaoling.androidclient", appContext.packageName)
|
||||
}
|
||||
}
|
||||
30
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/src/main/AndroidManifest.xml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@drawable/logo"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@drawable/logo"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AndroidClient"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AudioView extends View {
|
||||
|
||||
// 频谱数量
|
||||
private static final int LUMP_COUNT = 128;
|
||||
private static final int LUMP_WIDTH = 6;
|
||||
private static final int LUMP_SPACE = 2;
|
||||
private static final int LUMP_MIN_HEIGHT = LUMP_WIDTH;
|
||||
private static final int LUMP_MAX_HEIGHT = 200;//TODO: HEIGHT
|
||||
private static final int LUMP_SIZE = LUMP_WIDTH + LUMP_SPACE;
|
||||
private static final int LUMP_COLOR = Color.parseColor("#6de8fd");
|
||||
|
||||
private static final int WAVE_SAMPLING_INTERVAL = 3;
|
||||
|
||||
private static final float SCALE = LUMP_MAX_HEIGHT / LUMP_COUNT;
|
||||
|
||||
private ShowStyle upShowStyle = ShowStyle.STYLE_HOLLOW_LUMP;
|
||||
private ShowStyle downShowStyle = ShowStyle.STYLE_WAVE;
|
||||
|
||||
private byte[] waveData;
|
||||
List<Point> pointList;
|
||||
|
||||
private Paint lumpPaint;
|
||||
Path wavePath = new Path();
|
||||
|
||||
|
||||
public AudioView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public AudioView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public AudioView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
lumpPaint = new Paint();
|
||||
lumpPaint.setAntiAlias(true);
|
||||
lumpPaint.setColor(LUMP_COLOR);
|
||||
|
||||
lumpPaint.setStrokeWidth(2);
|
||||
lumpPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
public void setWaveData(byte[] data) {
|
||||
this.waveData = readyData(data);
|
||||
genSamplingPoint(data);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
public void setStyle(ShowStyle upShowStyle, ShowStyle downShowStyle) {
|
||||
this.upShowStyle = upShowStyle;
|
||||
this.downShowStyle = downShowStyle;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
wavePath.reset();
|
||||
|
||||
for (int i = 0; i < LUMP_COUNT; i++) {
|
||||
if (waveData == null) {
|
||||
canvas.drawRect((LUMP_WIDTH + LUMP_SPACE) * i,
|
||||
LUMP_MAX_HEIGHT - LUMP_MIN_HEIGHT,
|
||||
(LUMP_WIDTH + LUMP_SPACE) * i + LUMP_WIDTH,
|
||||
LUMP_MAX_HEIGHT,
|
||||
lumpPaint);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (upShowStyle) {
|
||||
case STYLE_HOLLOW_LUMP:
|
||||
drawLump(canvas, i, false);
|
||||
break;
|
||||
case STYLE_WAVE:
|
||||
drawWave(canvas, i, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (downShowStyle) {
|
||||
case STYLE_HOLLOW_LUMP:
|
||||
drawLump(canvas, i, true);
|
||||
break;
|
||||
case STYLE_WAVE:
|
||||
drawWave(canvas, i, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static byte[] readyData(byte[] fft) {
|
||||
byte[] newData = new byte[LUMP_COUNT];
|
||||
byte abs;
|
||||
for (int i = 0; i < LUMP_COUNT; i++) {
|
||||
abs = (byte) Math.abs(fft[i]);
|
||||
//描述:Math.abs -128时越界
|
||||
newData[i] = abs < 0 ? 127 : abs;
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制曲线
|
||||
*
|
||||
* @param canvas
|
||||
* @param i
|
||||
* @param reversal
|
||||
*/
|
||||
private void drawWave(Canvas canvas, int i, boolean reversal) {
|
||||
if (pointList == null || pointList.size() < 2) {
|
||||
return;
|
||||
}
|
||||
float ratio = SCALE * (reversal ? -1 : 1);
|
||||
if (i < pointList.size() - 2) {
|
||||
Point point = pointList.get(i);
|
||||
Point nextPoint = pointList.get(i + 1);
|
||||
int midX = (point.x + nextPoint.x) >> 1;
|
||||
if (i == 0) {
|
||||
wavePath.moveTo(point.x, LUMP_MAX_HEIGHT - point.y * ratio);
|
||||
}
|
||||
wavePath.cubicTo(midX, LUMP_MAX_HEIGHT - point.y * ratio,
|
||||
midX, LUMP_MAX_HEIGHT - nextPoint.y * ratio,
|
||||
nextPoint.x, LUMP_MAX_HEIGHT - nextPoint.y * ratio);
|
||||
|
||||
canvas.drawPath(wavePath, lumpPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制矩形条
|
||||
*/
|
||||
private void drawLump(Canvas canvas, int i, boolean reversal) {
|
||||
int minus = reversal ? -1 : 1;
|
||||
float top = (LUMP_MAX_HEIGHT - (LUMP_MIN_HEIGHT + waveData[i] * SCALE) * minus);
|
||||
|
||||
canvas.drawRect(LUMP_SIZE * i,
|
||||
top,
|
||||
LUMP_SIZE * i + LUMP_WIDTH,
|
||||
LUMP_MAX_HEIGHT,
|
||||
lumpPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成波形图的采样数据,减少计算量
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
private void genSamplingPoint(byte[] data) {
|
||||
if (upShowStyle != ShowStyle.STYLE_WAVE && downShowStyle != ShowStyle.STYLE_WAVE) {
|
||||
return;
|
||||
}
|
||||
if (pointList == null) {
|
||||
pointList = new ArrayList<>();
|
||||
} else {
|
||||
pointList.clear();
|
||||
}
|
||||
pointList.add(new Point(0, 0));
|
||||
for (int i = WAVE_SAMPLING_INTERVAL; i < LUMP_COUNT; i += WAVE_SAMPLING_INTERVAL) {
|
||||
pointList.add(new Point(LUMP_SIZE * i, waveData[i]));
|
||||
}
|
||||
pointList.add(new Point(LUMP_SIZE * LUMP_COUNT, 0));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 可视化样式
|
||||
*/
|
||||
public enum ShowStyle {
|
||||
/**
|
||||
* 空心的矩形小块
|
||||
*/
|
||||
STYLE_HOLLOW_LUMP,
|
||||
|
||||
/**
|
||||
* 曲线
|
||||
*/
|
||||
STYLE_WAVE,
|
||||
|
||||
/**
|
||||
* 不显示
|
||||
*/
|
||||
STYLE_NOTHING
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public static final String TAG = MainActivity.class.getSimpleName();
|
||||
// WebSocket地址
|
||||
public String ASR_HOST = "";
|
||||
// 官方WebSocket地址
|
||||
public static final String DEFAULT_HOST = "wss://101.37.77.25:10088";
|
||||
// 发送的JSON数据
|
||||
public static final String MODE = "2pass";
|
||||
public static final String CHUNK_SIZE = "5, 10, 5";
|
||||
public static final int CHUNK_INTERVAL = 10;
|
||||
public static final int SEND_SIZE = 1920;
|
||||
// 热词
|
||||
private String hotWords = "阿里巴巴 20\n达摩院 20\n夜雨飘零 20\n";
|
||||
// 采样率
|
||||
public static final int SAMPLE_RATE = 16000;
|
||||
// 声道数
|
||||
public static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
|
||||
// 返回的音频数据的格式
|
||||
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
|
||||
private AudioRecord audioRecord;
|
||||
private boolean isRecording = false;
|
||||
private AudioView audioView;
|
||||
private String allAsrText = "";
|
||||
private String asrText = "";
|
||||
private SharedPreferences sharedPreferences;
|
||||
// 控件
|
||||
private Button recordBtn;
|
||||
private TextView resultText;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
// 请求权限
|
||||
if (!hasPermission()) {
|
||||
requestPermission();
|
||||
}
|
||||
// 显示识别结果控件
|
||||
resultText = findViewById(R.id.result_text);
|
||||
// 显示录音状态控件
|
||||
audioView = findViewById(R.id.audioView);
|
||||
audioView.setStyle(AudioView.ShowStyle.STYLE_HOLLOW_LUMP, AudioView.ShowStyle.STYLE_NOTHING);
|
||||
// 按下识别按钮
|
||||
recordBtn = findViewById(R.id.record_button);
|
||||
recordBtn.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
if (!ASR_HOST.equals("")) {
|
||||
isRecording = false;
|
||||
stopRecording();
|
||||
recordBtn.setText("按下录音");
|
||||
}
|
||||
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (!ASR_HOST.equals("")) {
|
||||
allAsrText = "";
|
||||
asrText = "";
|
||||
isRecording = true;
|
||||
startRecording();
|
||||
recordBtn.setText("录音中...");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// 读取WebSocket地址
|
||||
sharedPreferences = getSharedPreferences("FunASR", MODE_PRIVATE);
|
||||
String uri = sharedPreferences.getString("uri", "");
|
||||
if (uri.equals("")) {
|
||||
showUriInput();
|
||||
} else {
|
||||
ASR_HOST = uri;
|
||||
}
|
||||
// 读取热词
|
||||
String hotWords = sharedPreferences.getString("hotwords", null);
|
||||
if (hotWords != null) {
|
||||
this.hotWords = hotWords;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.change_uri) {
|
||||
showUriInput();
|
||||
return true;
|
||||
} else if (id == R.id.change_hotwords) {
|
||||
showHotWordsInput();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// 显示WebSocket地址输入框
|
||||
private void showUriInput() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||
builder.setTitle("请输入WebSocket地址:");
|
||||
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_input_uri, null);
|
||||
final EditText input = view.findViewById(R.id.uri_edit_text);
|
||||
if (!ASR_HOST.equals("")) {
|
||||
input.setText(ASR_HOST);
|
||||
}
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton("确定", (dialog, id) -> {
|
||||
ASR_HOST = input.getText().toString();
|
||||
if (!ASR_HOST.equals("")) {
|
||||
Toast.makeText(MainActivity.this, "WebSocket地址:" + ASR_HOST, Toast.LENGTH_SHORT).show();
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("uri", ASR_HOST);
|
||||
editor.apply();
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton("使用官方服务", (dialog, id) -> {
|
||||
ASR_HOST = DEFAULT_HOST;
|
||||
input.setText(DEFAULT_HOST);
|
||||
Toast.makeText(MainActivity.this, "WebSocket地址:" + ASR_HOST, Toast.LENGTH_SHORT).show();
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("uri", ASR_HOST);
|
||||
editor.apply();
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 显示热词输入框
|
||||
private void showHotWordsInput() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
|
||||
builder.setTitle("请输入热词:");
|
||||
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_input_hotwords, null);
|
||||
final EditText input = view.findViewById(R.id.hotwords_edit_text);
|
||||
if (!this.hotWords.equals("")) {
|
||||
input.setText(this.hotWords);
|
||||
}
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton("确定", (dialog, id) -> {
|
||||
String hotwords = input.getText().toString();
|
||||
this.hotWords = hotwords;
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString("hotwords", hotwords);
|
||||
editor.apply();
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 开始录音
|
||||
private void startRecording() {
|
||||
// 准备录音器
|
||||
try {
|
||||
// 确保有权限
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermission();
|
||||
return;
|
||||
}
|
||||
// 创建录音器
|
||||
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, SEND_SIZE);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 开启一个线程将录音数据写入文件
|
||||
Thread recordingAudioThread = new Thread(() -> {
|
||||
try {
|
||||
setAudioData();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
recordingAudioThread.start();
|
||||
// 启动录音器
|
||||
audioRecord.startRecording();
|
||||
audioView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// 停止录音器
|
||||
private void stopRecording() {
|
||||
audioRecord.stop();
|
||||
audioRecord.release();
|
||||
audioRecord = null;
|
||||
audioView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// 读取录音数据
|
||||
private void setAudioData() throws Exception {
|
||||
// 建立WebSocket连接
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
// 忽略验证证书
|
||||
.sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
|
||||
// 不验证域名
|
||||
.hostnameVerifier(SSLSocketClient.getHostnameVerifier())
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(ASR_HOST)
|
||||
.build();
|
||||
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
|
||||
|
||||
@Override
|
||||
public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
|
||||
// 连接成功时的处理
|
||||
Log.d(TAG, "WebSocket连接成功");
|
||||
runOnUiThread(() -> Toast.makeText(MainActivity.this, "WebSocket连接成功", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
|
||||
// 接收到消息时的处理
|
||||
Log.d(TAG, "WebSocket接收到消息: " + text);
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(text);
|
||||
String t = jsonObject.getString("text");
|
||||
boolean isFinal = jsonObject.getBoolean("is_final");
|
||||
if (!t.equals("")) {
|
||||
// 拼接识别结果
|
||||
String mode = jsonObject.getString("mode");
|
||||
if (mode.equals("2pass-offline")) {
|
||||
asrText = "";
|
||||
allAsrText = allAsrText + t;
|
||||
// 这里可以做一些自动停止录音识别的程序
|
||||
} else {
|
||||
asrText = asrText + t;
|
||||
}
|
||||
}
|
||||
// 显示语音识别结果消息
|
||||
if (!(allAsrText + asrText).equals("")) {
|
||||
runOnUiThread(() -> resultText.setText(allAsrText + asrText));
|
||||
}
|
||||
// 如果检测的录音停止就关闭WebSocket连接
|
||||
if (isFinal) {
|
||||
webSocket.close(1000, "关闭WebSocket连接");
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
|
||||
// 关闭连接时的处理
|
||||
Log.d(TAG, "WebSocket关闭连接: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, Response response) {
|
||||
// 连接失败时的处理
|
||||
Log.d(TAG, "WebSocket连接失败: " + t + ": " + response);
|
||||
runOnUiThread(() -> Toast.makeText(MainActivity.this, "WebSocket连接失败:" + t, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
});
|
||||
String message = getMessage(true);
|
||||
Log.d(TAG, "WebSocket发送消息: " + message);
|
||||
webSocket.send(message);
|
||||
|
||||
audioRecord.startRecording();
|
||||
byte[] bytes = new byte[SEND_SIZE];
|
||||
while (isRecording) {
|
||||
int readSize = audioRecord.read(bytes, 0, SEND_SIZE);
|
||||
if (readSize > 0) {
|
||||
ByteString byteString = ByteString.of(bytes);
|
||||
webSocket.send(byteString);
|
||||
audioView.post(() -> audioView.setWaveData(bytes));
|
||||
}
|
||||
}
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("is_speaking", false);
|
||||
webSocket.send(obj.toString());
|
||||
// webSocket.close(1000, "关闭WebSocket连接");
|
||||
}
|
||||
|
||||
// 发送第一步的JSON数据
|
||||
public String getMessage(boolean isSpeaking) {
|
||||
try {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("mode", MODE);
|
||||
JSONArray array = new JSONArray();
|
||||
String[] chunkList = CHUNK_SIZE.split(",");
|
||||
for (String s : chunkList) {
|
||||
array.put(Integer.valueOf(s.trim()));
|
||||
}
|
||||
obj.put("chunk_size", array);
|
||||
obj.put("chunk_interval", CHUNK_INTERVAL);
|
||||
obj.put("wav_name", "default");
|
||||
if (!hotWords.equals("")) {
|
||||
JSONObject hotwordsJSON = new JSONObject();
|
||||
// 分割每一行字符串
|
||||
String[] hotWordsList = hotWords.split("\n");
|
||||
for (String s : hotWordsList) {
|
||||
if (s.equals("")) {
|
||||
Log.w(TAG, "hotWords为空");
|
||||
continue;
|
||||
}
|
||||
// 按照空格分割字符串
|
||||
String[] hotWordsArray = s.split(" ");
|
||||
if (hotWordsArray.length != 2) {
|
||||
Log.w(TAG, "hotWords格式不正确");
|
||||
continue;
|
||||
}
|
||||
hotwordsJSON.put(hotWordsArray[0], Integer.valueOf(hotWordsArray[1]));
|
||||
}
|
||||
obj.put("hotwords", hotwordsJSON.toString());
|
||||
}
|
||||
obj.put("wav_format", "pcm");
|
||||
obj.put("is_speaking", isSpeaking);
|
||||
return obj.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 检查权限
|
||||
private boolean hasPermission() {
|
||||
return checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
|
||||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
// 请求权限
|
||||
private void requestPermission() {
|
||||
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.yeyupiaoling.androidclient;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class SSLSocketClient {
|
||||
//获取SSLSocketFactory
|
||||
public static SSLSocketFactory getSSLSocketFactory() {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, getTrustManager(), new SecureRandom());
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//获取X509TrustManager
|
||||
public static X509TrustManager getX509TrustManager() {
|
||||
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
};
|
||||
return x509TrustManager;
|
||||
}
|
||||
|
||||
//获取TrustManager
|
||||
private static TrustManager[] getTrustManager() {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
};
|
||||
return trustAllCerts;
|
||||
}
|
||||
|
||||
//获取HostnameVerifier
|
||||
public static HostnameVerifier getHostnameVerifier() {
|
||||
HostnameVerifier hostnameVerifier = (s, sslSession) -> true;
|
||||
return hostnameVerifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="1dp" android:color="#000000" />
|
||||
<padding android:left="5dp" android:top="5dp" android:right="5dp" android:bottom="5dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<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">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
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"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
BIN
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/src/main/res/drawable/logo.png
vendored
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/record_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="按下录音" />
|
||||
|
||||
<com.yeyupiaoling.androidclient.AudioView
|
||||
android:id="@+id/audioView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_above="@id/record_button"
|
||||
android:layout_marginStart="10dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_text"
|
||||
android:layout_above="@id/record_button"
|
||||
android:layout_width="match_parent"
|
||||
android:hint="显示识别结果"
|
||||
android:textSize="22sp"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/hotwords_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_margin="10dp"
|
||||
android:inputType="textMultiLine"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:minLines="3"
|
||||
android:gravity="top"
|
||||
android:hint="每一行为:热词 权重" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/uri_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:background="@drawable/edittext_border"
|
||||
android:hint="wss://" />
|
||||
|
||||
</LinearLayout>
|
||||
9
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/src/main/res/menu/menu.xml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/change_uri"
|
||||
android:title="服务地址" />
|
||||
<item
|
||||
android:id="@+id/change_hotwords"
|
||||
android:title="热词" />
|
||||
</menu>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
10
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/src/main/res/values/colors.xml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">FunASR</string>
|
||||
</resources>
|
||||
16
modules/python/vendors/FunASR/runtime/android/AndroidClient/app/src/main/res/values/themes.xml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.AndroidClient" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!--
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yeyupiaoling.androidclient
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||