添加Android应用 (#1008)
* Add Android WebSocket Demo
* Add Android WebSocket Demo
* update docs
| New file |
| | |
| | | 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' |
| | | } |
| New file |
| | |
| | | # 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 |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | <?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="@mipmap/ic_launcher" |
| | | android:label="@string/app_name" |
| | | android:roundIcon="@mipmap/ic_launcher_round" |
| | | 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> |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | package com.yeyupiaoling.androidclient; |
| | | |
| | | import android.Manifest; |
| | | import android.annotation.SuppressLint; |
| | | 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.MotionEvent; |
| | | import android.view.View; |
| | | import android.widget.Button; |
| | | import android.widget.TextView; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.appcompat.app.AppCompatActivity; |
| | | import androidx.core.app.ActivityCompat; |
| | | |
| | | import org.json.JSONArray; |
| | | import org.json.JSONException; |
| | | import org.json.JSONObject; |
| | | |
| | | import javax.net.ssl.HostnameVerifier; |
| | | |
| | | 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地址,如果服务端没有使用SSL,请使用ws:// |
| | | public static final String ASR_HOST = "wss://192.168.0.1:10095"; |
| | | // 采样率 |
| | | 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 int minBufferSize; |
| | | private AudioView audioView; |
| | | private String allAsrText = ""; |
| | | private String asrText = ""; |
| | | // 控件 |
| | | private Button recordBtn; |
| | | private TextView resultText; |
| | | private WebSocket webSocket; |
| | | |
| | | @SuppressLint("ClickableViewAccessibility") |
| | | @Override |
| | | protected void onCreate(Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | setContentView(R.layout.activity_main); |
| | | // 请求权限 |
| | | if (!hasPermission()) { |
| | | requestPermission(); |
| | | } |
| | | // 录音参数 |
| | | minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, AUDIO_FORMAT); |
| | | // 显示识别结果控件 |
| | | 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) { |
| | | isRecording = false; |
| | | stopRecording(); |
| | | recordBtn.setText("按下录音"); |
| | | } else if (event.getAction() == MotionEvent.ACTION_DOWN) { |
| | | if (webSocket != null){ |
| | | webSocket.cancel(); |
| | | webSocket = null; |
| | | } |
| | | allAsrText = ""; |
| | | asrText = ""; |
| | | isRecording = true; |
| | | startRecording(); |
| | | recordBtn.setText("录音中..."); |
| | | } |
| | | return true; |
| | | }); |
| | | } |
| | | |
| | | // 开始录音 |
| | | private void startRecording() { |
| | | // 准备录音器 |
| | | try { |
| | | // 确保有权限 |
| | | if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { |
| | | requestPermission(); |
| | | return; |
| | | } |
| | | // 创建录音器 |
| | | audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL, AUDIO_FORMAT, minBufferSize); |
| | | } 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 { |
| | | // 如果使用正常的wss,可以去掉这个 |
| | | HostnameVerifier hostnameVerifier = (hostname, session) -> { |
| | | // 总是返回true,表示不验证域名 |
| | | return true; |
| | | }; |
| | | // 建立WebSocket连接 |
| | | OkHttpClient client = new OkHttpClient.Builder() |
| | | .hostnameVerifier(hostnameVerifier) |
| | | .build(); |
| | | Request request = new Request.Builder() |
| | | .url(ASR_HOST) |
| | | .build(); |
| | | webSocket = client.newWebSocket(request, new WebSocketListener() { |
| | | |
| | | @Override |
| | | public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) { |
| | | // 连接成功时的处理 |
| | | Log.d(TAG, "WebSocket连接成功"); |
| | | } |
| | | |
| | | @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); |
| | | } |
| | | }); |
| | | String message = getMessage("2pass", "5, 10, 5", 10, true); |
| | | webSocket.send(message); |
| | | |
| | | audioRecord.startRecording(); |
| | | byte[] bytes = new byte[minBufferSize]; |
| | | while (isRecording) { |
| | | int readSize = audioRecord.read(bytes, 0, minBufferSize); |
| | | 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(String mode, String strChunkSize, int chunkInterval, boolean isSpeaking) { |
| | | try { |
| | | JSONObject obj = new JSONObject(); |
| | | obj.put("mode", mode); |
| | | JSONArray array = new JSONArray(); |
| | | String[] chunkList = strChunkSize.split(","); |
| | | for (String s : chunkList) { |
| | | array.put(Integer.valueOf(s.trim())); |
| | | } |
| | | obj.put("chunk_size", array); |
| | | obj.put("chunk_interval", chunkInterval); |
| | | obj.put("wav_name", "default"); |
| | | // 热词 |
| | | obj.put("hotwords", "阿里巴巴 达摩院"); |
| | | obj.put("wav_format", "pcm"); |
| | | obj.put("is_speaking", isSpeaking); |
| | | return obj.toString(); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | // 检查权限 |
| | | private boolean hasPermission() { |
| | | return checkSelfPermission(android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED && |
| | | checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; |
| | | } |
| | | |
| | | // 请求权限 |
| | | private void requestPermission() { |
| | | requestPermissions(new String[]{android.Manifest.permission.RECORD_AUDIO, |
| | | Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); |
| | | } |
| | | } |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <resources> |
| | | <string name="app_name">FunASR</string> |
| | | </resources> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | <?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> |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | // Top-level build file where you can add configuration options common to all sub-projects/modules. |
| | | plugins { |
| | | id 'com.android.application' version '8.1.2' apply false |
| | | id 'org.jetbrains.kotlin.android' version '1.8.10' apply false |
| | | } |
| New file |
| | |
| | | # Project-wide Gradle settings. |
| | | # IDE (e.g. Android Studio) users: |
| | | # Gradle settings configured through the IDE *will override* |
| | | # any settings specified in this file. |
| | | # For more details on how to configure your build environment visit |
| | | # http://www.gradle.org/docs/current/userguide/build_environment.html |
| | | # Specifies the JVM arguments used for the daemon process. |
| | | # The setting is particularly useful for tweaking memory settings. |
| | | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
| | | # When configured, Gradle will run in incubating parallel mode. |
| | | # This option should only be used with decoupled projects. More details, visit |
| | | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects |
| | | # org.gradle.parallel=true |
| | | # AndroidX package structure to make it clearer which packages are bundled with the |
| | | # Android operating system, and which are packaged with your app's APK |
| | | # https://developer.android.com/topic/libraries/support-library/androidx-rn |
| | | android.useAndroidX=true |
| | | # Kotlin code style for this project: "official" or "obsolete": |
| | | kotlin.code.style=official |
| | | # Enables namespacing of each library's R class so that its R class includes only the |
| | | # resources declared in the library itself and none from the library's dependencies, |
| | | # thereby reducing the size of the R class for that library |
| | | android.nonTransitiveRClass=true |
| New file |
| | |
| | | #Fri Oct 13 14:55:29 CST 2023 |
| | | distributionBase=GRADLE_USER_HOME |
| | | distributionPath=wrapper/dists |
| | | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip |
| | | zipStoreBase=GRADLE_USER_HOME |
| | | zipStorePath=wrapper/dists |
| New file |
| | |
| | | #!/usr/bin/env sh |
| | | |
| | | # |
| | | # Copyright 2015 the original author or authors. |
| | | # |
| | | # Licensed under the Apache License, Version 2.0 (the "License"); |
| | | # you may not use this file except in compliance with the License. |
| | | # You may obtain a copy of the License at |
| | | # |
| | | # https://www.apache.org/licenses/LICENSE-2.0 |
| | | # |
| | | # Unless required by applicable law or agreed to in writing, software |
| | | # distributed under the License is distributed on an "AS IS" BASIS, |
| | | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | # See the License for the specific language governing permissions and |
| | | # limitations under the License. |
| | | # |
| | | |
| | | ############################################################################## |
| | | ## |
| | | ## Gradle start up script for UN*X |
| | | ## |
| | | ############################################################################## |
| | | |
| | | # Attempt to set APP_HOME |
| | | # Resolve links: $0 may be a link |
| | | PRG="$0" |
| | | # Need this for relative symlinks. |
| | | while [ -h "$PRG" ] ; do |
| | | ls=`ls -ld "$PRG"` |
| | | link=`expr "$ls" : '.*-> \(.*\)$'` |
| | | if expr "$link" : '/.*' > /dev/null; then |
| | | PRG="$link" |
| | | else |
| | | PRG=`dirname "$PRG"`"/$link" |
| | | fi |
| | | done |
| | | SAVED="`pwd`" |
| | | cd "`dirname \"$PRG\"`/" >/dev/null |
| | | APP_HOME="`pwd -P`" |
| | | cd "$SAVED" >/dev/null |
| | | |
| | | APP_NAME="Gradle" |
| | | APP_BASE_NAME=`basename "$0"` |
| | | |
| | | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
| | | |
| | | # Use the maximum available, or set MAX_FD != -1 to use that value. |
| | | MAX_FD="maximum" |
| | | |
| | | warn () { |
| | | echo "$*" |
| | | } |
| | | |
| | | die () { |
| | | echo |
| | | echo "$*" |
| | | echo |
| | | exit 1 |
| | | } |
| | | |
| | | # OS specific support (must be 'true' or 'false'). |
| | | cygwin=false |
| | | msys=false |
| | | darwin=false |
| | | nonstop=false |
| | | case "`uname`" in |
| | | CYGWIN* ) |
| | | cygwin=true |
| | | ;; |
| | | Darwin* ) |
| | | darwin=true |
| | | ;; |
| | | MINGW* ) |
| | | msys=true |
| | | ;; |
| | | NONSTOP* ) |
| | | nonstop=true |
| | | ;; |
| | | esac |
| | | |
| | | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
| | | |
| | | |
| | | # Determine the Java command to use to start the JVM. |
| | | if [ -n "$JAVA_HOME" ] ; then |
| | | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
| | | # IBM's JDK on AIX uses strange locations for the executables |
| | | JAVACMD="$JAVA_HOME/jre/sh/java" |
| | | else |
| | | JAVACMD="$JAVA_HOME/bin/java" |
| | | fi |
| | | if [ ! -x "$JAVACMD" ] ; then |
| | | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | else |
| | | JAVACMD="java" |
| | | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | |
| | | Please set the JAVA_HOME variable in your environment to match the |
| | | location of your Java installation." |
| | | fi |
| | | |
| | | # Increase the maximum file descriptors if we can. |
| | | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
| | | MAX_FD_LIMIT=`ulimit -H -n` |
| | | if [ $? -eq 0 ] ; then |
| | | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
| | | MAX_FD="$MAX_FD_LIMIT" |
| | | fi |
| | | ulimit -n $MAX_FD |
| | | if [ $? -ne 0 ] ; then |
| | | warn "Could not set maximum file descriptor limit: $MAX_FD" |
| | | fi |
| | | else |
| | | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
| | | fi |
| | | fi |
| | | |
| | | # For Darwin, add options to specify how the application appears in the dock |
| | | if $darwin; then |
| | | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
| | | fi |
| | | |
| | | # For Cygwin or MSYS, switch paths to Windows format before running java |
| | | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
| | | APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
| | | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
| | | |
| | | JAVACMD=`cygpath --unix "$JAVACMD"` |
| | | |
| | | # We build the pattern for arguments to be converted via cygpath |
| | | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
| | | SEP="" |
| | | for dir in $ROOTDIRSRAW ; do |
| | | ROOTDIRS="$ROOTDIRS$SEP$dir" |
| | | SEP="|" |
| | | done |
| | | OURCYGPATTERN="(^($ROOTDIRS))" |
| | | # Add a user-defined pattern to the cygpath arguments |
| | | if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
| | | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
| | | fi |
| | | # Now convert the arguments - kludge to limit ourselves to /bin/sh |
| | | i=0 |
| | | for arg in "$@" ; do |
| | | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
| | | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
| | | |
| | | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
| | | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
| | | else |
| | | eval `echo args$i`="\"$arg\"" |
| | | fi |
| | | i=`expr $i + 1` |
| | | done |
| | | case $i in |
| | | 0) set -- ;; |
| | | 1) set -- "$args0" ;; |
| | | 2) set -- "$args0" "$args1" ;; |
| | | 3) set -- "$args0" "$args1" "$args2" ;; |
| | | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
| | | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
| | | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
| | | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
| | | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
| | | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
| | | esac |
| | | fi |
| | | |
| | | # Escape application args |
| | | save () { |
| | | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
| | | echo " " |
| | | } |
| | | APP_ARGS=`save "$@"` |
| | | |
| | | # Collect all arguments for the java command, following the shell quoting and substitution rules |
| | | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
| | | |
| | | exec "$JAVACMD" "$@" |
| New file |
| | |
| | | @rem |
| | | @rem Copyright 2015 the original author or authors. |
| | | @rem |
| | | @rem Licensed under the Apache License, Version 2.0 (the "License"); |
| | | @rem you may not use this file except in compliance with the License. |
| | | @rem You may obtain a copy of the License at |
| | | @rem |
| | | @rem https://www.apache.org/licenses/LICENSE-2.0 |
| | | @rem |
| | | @rem Unless required by applicable law or agreed to in writing, software |
| | | @rem distributed under the License is distributed on an "AS IS" BASIS, |
| | | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | @rem See the License for the specific language governing permissions and |
| | | @rem limitations under the License. |
| | | @rem |
| | | |
| | | @if "%DEBUG%" == "" @echo off |
| | | @rem ########################################################################## |
| | | @rem |
| | | @rem Gradle startup script for Windows |
| | | @rem |
| | | @rem ########################################################################## |
| | | |
| | | @rem Set local scope for the variables with windows NT shell |
| | | if "%OS%"=="Windows_NT" setlocal |
| | | |
| | | set DIRNAME=%~dp0 |
| | | if "%DIRNAME%" == "" set DIRNAME=. |
| | | set APP_BASE_NAME=%~n0 |
| | | set APP_HOME=%DIRNAME% |
| | | |
| | | @rem Resolve any "." and ".." in APP_HOME to make it shorter. |
| | | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
| | | |
| | | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
| | | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
| | | |
| | | @rem Find java.exe |
| | | if defined JAVA_HOME goto findJavaFromJavaHome |
| | | |
| | | set JAVA_EXE=java.exe |
| | | %JAVA_EXE% -version >NUL 2>&1 |
| | | if "%ERRORLEVEL%" == "0" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :findJavaFromJavaHome |
| | | set JAVA_HOME=%JAVA_HOME:"=% |
| | | set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
| | | |
| | | if exist "%JAVA_EXE%" goto execute |
| | | |
| | | echo. |
| | | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
| | | echo. |
| | | echo Please set the JAVA_HOME variable in your environment to match the |
| | | echo location of your Java installation. |
| | | |
| | | goto fail |
| | | |
| | | :execute |
| | | @rem Setup the command line |
| | | |
| | | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
| | | |
| | | |
| | | @rem Execute Gradle |
| | | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* |
| | | |
| | | :end |
| | | @rem End local scope for the variables with windows NT shell |
| | | if "%ERRORLEVEL%"=="0" goto mainEnd |
| | | |
| | | :fail |
| | | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
| | | rem the _cmd.exe /c_ return code! |
| | | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
| | | exit /b 1 |
| | | |
| | | :mainEnd |
| | | if "%OS%"=="Windows_NT" endlocal |
| | | |
| | | :omega |
| New file |
| | |
| | | pluginManagement { |
| | | repositories { |
| | | google() |
| | | mavenCentral() |
| | | gradlePluginPortal() |
| | | } |
| | | } |
| | | dependencyResolutionManagement { |
| | | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) |
| | | repositories { |
| | | google() |
| | | mavenCentral() |
| | | } |
| | | } |
| | | |
| | | rootProject.name = "AndroidClient" |
| | | include ':app' |
| New file |
| | |
| | | # AndroidClient |
| | | |
| | | 先说明,本项目是使用WebSocket连接服务器的语音识别服务,并不是将FunASR部署到Android里,服务启动方式请查看文档[SDK_advanced_guide_online_zh.md](https://github.com/alibaba-damo-academy/FunASR/blob/main/funasr/runtime/docs/SDK_advanced_guide_online_zh.md)。 |
| | | |
| | | 使用最新的 Android Studio 打开`AndroidClient`项目,运行即可,在运行之前还需要修改`ASR_HOST`参数,该参数是语音识别服务的WebSocket接口地址,需要修复为开发者自己的服务地址。 |
| | | |
| | | 应用只有一个功能,按钮下开始识别,松开按钮结束识别。 |
| | | |
| | | 应用效果图: |
| | | |
| | | <div align="center"> |
| | | <img src="./images/demo.png" alt="应用效果图" width="300"> |
| | | </div> |