├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── drawable │ │ │ ├── base_record_pause.png │ │ │ ├── base_record_play.png │ │ │ ├── base_recording_stop.png │ │ │ ├── base_recording_close.png │ │ │ ├── base_recording_start.png │ │ │ ├── background_rectangle.xml │ │ │ └── ic_launcher_background.xml │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ │ └── themes.xml │ │ └── layout │ │ │ ├── fragment_dialog_recording.xml │ │ │ ├── fragment_dialog_play.xml │ │ │ ├── activity_sound_touch.xml │ │ │ ├── activity_sound_fmod.xml │ │ │ └── activity_main.xml │ │ ├── java │ │ └── com │ │ │ └── demon │ │ │ └── changevoice │ │ │ ├── record │ │ │ ├── AudioRecordManager.kt │ │ │ ├── IAudioRecord.kt │ │ │ ├── RecordingService.kt │ │ │ ├── AudioRecordAmr.kt │ │ │ ├── AudioRecordWav.kt │ │ │ ├── RecordingDialogFragment.kt │ │ │ └── PlayDialogFragment.kt │ │ │ ├── App.kt │ │ │ ├── Ext.kt │ │ │ ├── SPUtil.kt │ │ │ ├── SoundFmodActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SoundTouchActivity.kt │ │ │ └── widget │ │ │ └── FNRadioGroup.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── SoundCoding ├── .gitignore ├── consumer-rules.pro ├── SoundCoding.png ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── demon │ │ └── soundcoding │ │ ├── WavToPcm.java │ │ ├── AmrToWav.java │ │ ├── WaveHeader.java │ │ ├── WavToAmr.java │ │ ├── PcmToAmr.java │ │ ├── PcmToWav.java │ │ └── AmrToPcm.java ├── build.gradle └── proguard-rules.pro ├── SoundTouch ├── .gitignore ├── consumer-rules.pro ├── libs │ ├── x86 │ │ └── libsoundtouch.so │ ├── x86_64 │ │ └── libsoundtouch.so │ ├── arm64-v8a │ │ └── libsoundtouch.so │ ├── armeabi │ │ └── libsoundtouch.so │ └── armeabi-v7a │ │ └── libsoundtouch.so ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── net │ │ └── surina │ │ └── soundtouch │ │ └── SoundTouch.java ├── build.gradle └── proguard-rules.pro ├── FmodSound ├── .gitignore ├── src │ └── main │ │ ├── cpp │ │ ├── inc │ │ │ ├── fmod_android.h │ │ │ ├── fmod_codec.h │ │ │ ├── fmod_output.h │ │ │ ├── fmod_errors.h │ │ │ └── fmod_dsp_effects.h │ │ └── native-lib.cpp │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── demon │ │ └── fmodsound │ │ └── FmodSound.kt ├── libs │ ├── fmod.jar │ ├── x86 │ │ ├── libfmod.so │ │ └── libfmodL.so │ ├── x86_64 │ │ ├── libfmod.so │ │ └── libfmodL.so │ ├── armeabi │ │ ├── libfmod.so │ │ └── libfmodL.so │ ├── arm64-v8a │ │ ├── libfmod.so │ │ └── libfmodL.so │ └── armeabi-v7a │ │ ├── libfmod.so │ │ └── libfmodL.so ├── build.gradle └── CMakeLists.txt ├── .idea ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── compiler.xml ├── misc.xml ├── gradle.xml └── jarRepositories.xml ├── screen ├── 111.jpg ├── 222.jpg └── 333.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── gradle.properties ├── Readme.md ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /SoundCoding/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /SoundTouch/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /SoundCoding/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SoundTouch/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /FmodSound/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.cxx -------------------------------------------------------------------------------- /FmodSound/src/main/cpp/inc/fmod_android.h: -------------------------------------------------------------------------------- 1 | /* Placeholder */ 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /screen/111.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/screen/111.jpg -------------------------------------------------------------------------------- /screen/222.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/screen/222.jpg -------------------------------------------------------------------------------- /screen/333.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/screen/333.jpg -------------------------------------------------------------------------------- /FmodSound/libs/fmod.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/fmod.jar -------------------------------------------------------------------------------- /SoundCoding/SoundCoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundCoding/SoundCoding.png -------------------------------------------------------------------------------- /FmodSound/libs/x86/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/x86/libfmod.so -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeMonSound 3 | -------------------------------------------------------------------------------- /FmodSound/libs/x86/libfmodL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/x86/libfmodL.so -------------------------------------------------------------------------------- /FmodSound/libs/x86_64/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/x86_64/libfmod.so -------------------------------------------------------------------------------- /FmodSound/libs/armeabi/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/armeabi/libfmod.so -------------------------------------------------------------------------------- /FmodSound/libs/armeabi/libfmodL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/armeabi/libfmodL.so -------------------------------------------------------------------------------- /FmodSound/libs/x86_64/libfmodL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/x86_64/libfmodL.so -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /FmodSound/libs/arm64-v8a/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/arm64-v8a/libfmod.so -------------------------------------------------------------------------------- /FmodSound/libs/arm64-v8a/libfmodL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/arm64-v8a/libfmodL.so -------------------------------------------------------------------------------- /FmodSound/libs/armeabi-v7a/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/armeabi-v7a/libfmod.so -------------------------------------------------------------------------------- /SoundTouch/libs/x86/libsoundtouch.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundTouch/libs/x86/libsoundtouch.so -------------------------------------------------------------------------------- /FmodSound/libs/armeabi-v7a/libfmodL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/FmodSound/libs/armeabi-v7a/libfmodL.so -------------------------------------------------------------------------------- /SoundTouch/libs/x86_64/libsoundtouch.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundTouch/libs/x86_64/libsoundtouch.so -------------------------------------------------------------------------------- /SoundTouch/libs/arm64-v8a/libsoundtouch.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundTouch/libs/arm64-v8a/libsoundtouch.so -------------------------------------------------------------------------------- /SoundTouch/libs/armeabi/libsoundtouch.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundTouch/libs/armeabi/libsoundtouch.so -------------------------------------------------------------------------------- /SoundTouch/libs/armeabi-v7a/libsoundtouch.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/SoundTouch/libs/armeabi-v7a/libsoundtouch.so -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/base_record_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/drawable/base_record_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/base_record_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/drawable/base_record_play.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':SoundCoding' 2 | include ':FmodSound' 3 | include ':app' 4 | rootProject.name = "DeMon_Sound" 5 | include ':SoundTouch' 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/base_recording_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/drawable/base_recording_stop.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/base_recording_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/drawable/base_recording_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/base_recording_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/drawable/base_recording_start.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FmodSound/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /SoundCoding/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /SoundTouch/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 29 15:54:05 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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 16 | -------------------------------------------------------------------------------- /SoundCoding/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion androidCompileVersion 8 | 9 | defaultConfig { 10 | minSdkVersion androidMinSdkVersion 11 | targetSdkVersion androidTargetVersion 12 | } 13 | } 14 | 15 | dependencies { 16 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /SoundTouch/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion androidCompileVersion 8 | 9 | defaultConfig { 10 | minSdkVersion androidMinSdkVersion 11 | targetSdkVersion androidTargetVersion 12 | } 13 | sourceSets { 14 | main { 15 | jniLibs.srcDirs = ['libs'] 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | } -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /FmodSound/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion androidCompileVersion 8 | 9 | defaultConfig { 10 | minSdkVersion androidMinSdkVersion 11 | targetSdkVersion androidTargetVersion 12 | } 13 | 14 | externalNativeBuild { 15 | cmake { 16 | path "CMakeLists.txt" 17 | version "3.10.2" 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | api files('libs/fmod.jar') 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/record/AudioRecordManager.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice.record 2 | 3 | /** 4 | * @author DeMon 5 | * Created on 2021/4/29. 6 | * E-mail 757454343@qq.com 7 | * Desc: 8 | */ 9 | object AudioRecordManager { 10 | 11 | const val AMR = 0 12 | const val PCM = 1 13 | 14 | fun getInstance(mode: Int): IAudioRecord { 15 | return when (mode) { 16 | AMR -> AudioRecordAmr() 17 | PCM -> AudioRecordWav() 18 | else -> AudioRecordAmr() 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | 12 | #81D4FA 13 | #03A9F4 14 | #0288D1 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/App.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | 7 | /** 8 | * @author DeMon 9 | * Created on 2021/4/29. 10 | * E-mail 757454343@qq.com 11 | * Desc: 12 | */ 13 | class App : Application() { 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | mContext = this.applicationContext 18 | } 19 | 20 | 21 | companion object { 22 | @SuppressLint("StaticFieldLeak") 23 | lateinit var mContext: Context 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/record/IAudioRecord.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice.record 2 | 3 | /** 4 | * @author DeMon 5 | * Created on 2021/4/29. 6 | * E-mail 757454343@qq.com 7 | * Desc: 8 | */ 9 | interface IAudioRecord { 10 | 11 | fun setRecordListener(listener: RecordListener?) 12 | 13 | fun startRecording(outFile: String) 14 | 15 | fun stopRecording() 16 | 17 | fun getOutFilePath(): String 18 | 19 | 20 | interface RecordListener { 21 | fun start() 22 | 23 | fun volume(volume: Int) 24 | 25 | fun stop(outPath: String) 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /SoundCoding/proguard-rules.pro: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /SoundTouch/proguard-rules.pro: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 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. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec: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 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /FmodSound/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.10.2) 7 | 8 | #----------------------------------------- 9 | find_library( log-lib 10 | log ) 11 | 12 | set(lib_path ${CMAKE_SOURCE_DIR}/libs) 13 | # 添加三方的so库 14 | add_library(libfmod 15 | SHARED 16 | IMPORTED ) 17 | 18 | # 指名第三方库的绝对路径 19 | set_target_properties( libfmod 20 | PROPERTIES IMPORTED_LOCATION 21 | ${lib_path}/${ANDROID_ABI}/libfmod.so ) 22 | 23 | add_library(libfmodL 24 | SHARED 25 | IMPORTED ) 26 | 27 | set_target_properties( libfmodL 28 | PROPERTIES IMPORTED_LOCATION 29 | ${lib_path}/${ANDROID_ABI}/libfmodL.so ) 30 | 31 | #-------------------------------- 32 | add_library( # Sets the name of the library. 33 | FmodSound 34 | # Sets the library as a shared library. 35 | SHARED 36 | # Provides a relative path to your source file(s). 37 | src/main/cpp/native-lib.cpp) 38 | 39 | #--------------------- 40 | # 导入路径,为了让编译时能够寻找到这个文件夹 41 | include_directories(src/main/cpp/inc) 42 | 43 | # 链接好三个路径 44 | target_link_libraries( FmodSound 45 | libfmod 46 | libfmodL 47 | ${log-lib} ) -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /SoundCoding/src/main/java/com/demon/soundcoding/WavToPcm.java: -------------------------------------------------------------------------------- 1 | package com.demon.soundcoding; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * @author DeMon 7 | * Created on 2021/4/28. 8 | * E-mail 757454343@qq.com 9 | * Desc: 10 | */ 11 | public class WavToPcm { 12 | private static final String TAG = "WavToPcm"; 13 | 14 | /** 15 | * WAV转PCM,模式使用WAV文件夹及文件名 16 | * 17 | * @param wavPath wav文件路径 18 | * @param isDeleteWav 转码成功后是否删除wav文件 19 | * @return 转码后的pcm路径 20 | */ 21 | public static String makeWavToPcm(String wavPath, boolean isDeleteWav) { 22 | //建立输出文件 23 | String pcmPath = ""; 24 | if (wavPath.endsWith(".wav")) { 25 | wavPath = wavPath.replace(".wav", ".pcm"); 26 | } else { 27 | wavPath = wavPath + ".pcm"; 28 | } 29 | return makeWavToPcm(wavPath, pcmPath, isDeleteWav); 30 | } 31 | 32 | 33 | /** 34 | * WAV转PCM 35 | * 36 | * @param wavPath wav文件路径 37 | * @param pcmPath pcm文件路径 38 | * @param isDeleteWav 转码成功后是否删除wav文件 39 | * @return 转码后的pcm路径 40 | */ 41 | public static String makeWavToPcm(String wavPath, String pcmPath, boolean isDeleteWav) { 42 | Log.i(TAG, "makeAmrToPcm: wavPath=" + wavPath + ",pcmPath=" + pcmPath + ",isDeleteWav=" + isDeleteWav); 43 | String amrPath = WavToAmr.makeWavToAmr(wavPath, isDeleteWav); 44 | return AmrToPcm.makeAmrToPcm(amrPath, pcmPath, true); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SoundCoding/src/main/java/com/demon/soundcoding/AmrToWav.java: -------------------------------------------------------------------------------- 1 | package com.demon.soundcoding; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * @author DeMon 7 | * Created on 2021/4/28. 8 | * E-mail 757454343@qq.com 9 | * Desc: 10 | */ 11 | public class AmrToWav { 12 | 13 | private static final String TAG = "SoundCoding_AmrToWav"; 14 | 15 | /** 16 | * AMR转WAV,默认使用amr所在文件夹及文件名 17 | * 18 | * @param amrPath amr文件路径 19 | * @param isDeleteAmr 转码成功后是否删除amr文件 20 | * @return 转码后的wav路径 21 | */ 22 | public static String makeAmrToWav(String amrPath, boolean isDeleteAmr) { 23 | //建立输出文件 24 | String wavPath = ""; 25 | if (amrPath.endsWith(".amr")) { 26 | wavPath = amrPath.replace(".amr", ".wav"); 27 | } else { 28 | wavPath = amrPath + ".wav"; 29 | } 30 | return makeAmrToWav(amrPath, wavPath, isDeleteAmr); 31 | } 32 | 33 | 34 | /** 35 | * AMR转WAV 36 | * 37 | * @param amrPath amr文件路径 38 | * @param wavPath wav文件路径 39 | * @param isDeleteAmr 转码成功后是否删除amr文件 40 | * @return 转码后的wav路径 41 | */ 42 | public static String makeAmrToWav(String amrPath, String wavPath, boolean isDeleteAmr) { 43 | Log.i(TAG, "makeAmrToPcm: amrPath=" + amrPath + ",wavPath=" + wavPath + ",isDeleteAmr=" + isDeleteAmr); 44 | String pcmPath = AmrToPcm.makeAmrToPcm(amrPath, isDeleteAmr); 45 | return PcmToWav.makePcmToWav(pcmPath, wavPath, true); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog_recording.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 26 | 27 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/Ext.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice 2 | 3 | import android.content.Context 4 | import android.os.Environment 5 | import android.widget.Toast 6 | import androidx.fragment.app.Fragment 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.Job 10 | import kotlinx.coroutines.launch 11 | import java.io.File 12 | 13 | /** 14 | * @author DeMon 15 | * Created on 2020/10/23. 16 | * E-mail 757454343@qq.com 17 | * Desc: 18 | */ 19 | 20 | /** 21 | * io线程 22 | */ 23 | fun CoroutineScope.launchIO(block: suspend () -> Unit): Job { 24 | return this.launch(Dispatchers.IO) { block() } 25 | } 26 | 27 | /** 28 | * 主线程 29 | */ 30 | fun CoroutineScope.launchUI(block: suspend () -> Unit): Job { 31 | return this.launch(Dispatchers.Main) { block() } 32 | } 33 | 34 | fun Fragment.showToast(text: String) { 35 | Toast.makeText(requireContext(), text, Toast.LENGTH_SHORT).show() 36 | } 37 | 38 | fun Context.showToast(text: String) { 39 | Toast.makeText(this, text, Toast.LENGTH_SHORT).show() 40 | } 41 | 42 | fun Fragment.getRecordFilePath(type: Int = 0) = this.requireContext().getRecordFilePath(type) 43 | 44 | fun Context.getRecordFilePath(type: Int = 0): String { 45 | val suffix = when (type) { 46 | 1 -> ".wav" 47 | 2 -> ".pcm" 48 | else -> ".amr" 49 | } 50 | val file = File(this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath + "/Record") 51 | if (!file.exists()) { 52 | file.mkdirs() 53 | } 54 | val path = File(file, "${System.currentTimeMillis()}$suffix") 55 | if (!path.exists()) { 56 | file.createNewFile() 57 | } 58 | return path.absolutePath 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/record/RecordingService.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice.record 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.media.MediaRecorder 6 | import android.os.Handler 7 | import android.os.IBinder 8 | import android.util.Log 9 | import java.io.File 10 | import kotlin.math.log10 11 | 12 | /** 13 | * 使用MediaRecorder录制AMR 14 | * Created by DeMon on 2018/7/19. 15 | */ 16 | class RecordingService : Service() { 17 | 18 | private var audioRecord: IAudioRecord? = null 19 | 20 | override fun onBind(intent: Intent): IBinder? { 21 | return null 22 | } 23 | 24 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { 25 | val mFilePath = intent.getStringExtra("FilePath") ?: "" 26 | val type = intent.getIntExtra("Type", 0) 27 | if (audioRecord == null) { 28 | audioRecord = AudioRecordManager.getInstance(type) 29 | audioRecord?.setRecordListener(object : IAudioRecord.RecordListener { 30 | override fun start() { 31 | Log.i(TAG, "start: ") 32 | } 33 | 34 | override fun volume(volume: Int) { 35 | Log.i(TAG, "volume: $volume") 36 | } 37 | 38 | override fun stop(outPath: String) { 39 | Log.i(TAG, "stop: $outPath") 40 | } 41 | }) 42 | } 43 | audioRecord?.startRecording(mFilePath) 44 | return START_STICKY 45 | } 46 | 47 | override fun onDestroy() { 48 | super.onDestroy() 49 | audioRecord?.stopRecording() 50 | } 51 | 52 | 53 | companion object { 54 | private const val TAG = "RecordingService" 55 | } 56 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion androidCompileVersion 8 | defaultConfig { 9 | applicationId "com.demon.changevoice" 10 | minSdkVersion androidMinSdkVersion 11 | targetSdkVersion androidTargetVersion 12 | versionCode 1 13 | versionName "1.0" 14 | ndk { 15 | abiFilters "arm64-v8a","armeabi-v7a" 16 | } 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = '1.8' 30 | } 31 | buildFeatures { 32 | viewBinding = true 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 38 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" 39 | implementation 'androidx.core:core-ktx:1.7.0' 40 | implementation 'com.google.android.material:material:1.4.0' 41 | implementation 'androidx.appcompat:appcompat:1.3.1' 42 | implementation 'com.google.android.material:material:1.4.0' 43 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 44 | implementation 'com.guolindev.permissionx:permissionx:1.6.1' 45 | implementation project(path: ':FmodSound') 46 | implementation project(path: ':SoundTouch') 47 | implementation project(path: ':SoundCoding') 48 | //implementation 'com.github.iDeMonnnnnn.DeMon_Sound:FmodSound:1.0' 49 | //implementation 'com.github.iDeMonnnnnn.DeMon_Sound:SoundTouch:1.0' 50 | //implementation 'com.github.iDeMonnnnnn.DeMon_Sound:SoundCoding:1.0' 51 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ### DeMon_Sound 2 | 3 | [![](https://jitpack.io/v/iDeMonnnnnn/DeMon_Sound.svg)](https://jitpack.io/#iDeMonnnnnn/DeMon_Sound) 4 | 5 | Android端基于[FMOD](https://www.fmod.com/)/[SoundTouch](https://gitlab.com/soundtouch/soundtouch)的简单音频变声解决方案。 6 | 7 | 1. FmodSound库提供了基于FMOD的7种变声方案。 8 | 2. SoundTouch库提供了音调&变速设置的变声方案。 9 | 3. SoundCoding库提供了```AMR<-->PCM<-->WAV```互转方案。 10 | 11 | #### 开始使用 12 | 13 | ##### 使用文档 14 | 15 | [WIKI](https://github.com/iDeMonnnnnn/DeMon_Sound/wiki) 16 | 17 | ##### 添加依赖 18 | 19 | ``` 20 | allprojects { 21 | repositories { 22 | ... 23 | maven { url 'https://jitpack.io' } 24 | } 25 | } 26 | ``` 27 | [latest_version](https://github.com/iDeMonnnnnn/DeMon_Sound/releases) 28 | ``` 29 | dependencies { 30 | //使用FMOD变声,请添加此库 31 | implementation 'com.github.iDeMonnnnnn.DeMon_Sound:FmodSound:$latest_version' 32 | //使用SoundTouch变声,请添加此库 33 | implementation 'com.github.iDeMonnnnnn.DeMon_Sound:SoundTouch:$latest_version' 34 | //使用视频转码功能,请添加此库 35 | implementation 'com.github.iDeMonnnnnn.DeMon_Sound:SoundCoding:$latest_version' 36 | } 37 | ``` 38 | 39 | ##### 示例代码 40 | 41 | [App示例代码](https://github.com/iDeMonnnnnn/DeMon_Sound/tree/master/app) 42 | 43 | [Apk体验](https://raw.githubusercontent.com/iDeMonnnnnn/DeMon_Sound/master/app-release.apk) 44 | 45 | ##### 截图 46 | 47 | ![](https://github.com/iDeMonnnnnn/DeMon_Sound/blob/master/screen/111.jpg?raw=true) 48 | ![](https://github.com/iDeMonnnnnn/DeMon_Sound/blob/master/screen/222.jpg?raw=true) 49 | ![](https://github.com/iDeMonnnnnn/DeMon_Sound/blob/master/screen/333.jpg?raw=true) 50 | 51 | ### License 52 | 53 | ``` 54 | Copyright [2022] [DeMon] 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | 68 | ``` 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /SoundTouch/src/main/java/net/surina/soundtouch/SoundTouch.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | /// 3 | /// Example class that invokes native SoundTouch routines through the JNI 4 | /// interface. 5 | /// 6 | /// Author : Copyright (c) Olli Parviainen 7 | /// Author e-mail : oparviai 'at' iki.fi 8 | /// WWW : http://www.surina.net 9 | /// 10 | //////////////////////////////////////////////////////////////////////////////// 11 | 12 | package net.surina.soundtouch; 13 | 14 | public final class SoundTouch 15 | { 16 | // Native interface function that returns SoundTouch version string. 17 | // This invokes the native c++ routine defined in "soundtouch-jni.cpp". 18 | public native final static String getVersionString(); 19 | 20 | private native final void setTempo(long handle, float tempo); 21 | 22 | private native final void setPitchSemiTones(long handle, float pitch); 23 | 24 | private native final void setSpeed(long handle, float speed); 25 | 26 | private native final int processFile(long handle, String inputFile, String outputFile); 27 | 28 | public native final static String getErrorString(); 29 | 30 | private native final static long newInstance(); 31 | 32 | private native final void deleteInstance(long handle); 33 | 34 | long handle = 0; 35 | 36 | 37 | public SoundTouch() 38 | { 39 | handle = newInstance(); 40 | } 41 | 42 | 43 | public void close() 44 | { 45 | deleteInstance(handle); 46 | handle = 0; 47 | } 48 | 49 | 50 | public void setTempo(float tempo) 51 | { 52 | setTempo(handle, tempo); 53 | } 54 | 55 | 56 | public void setPitchSemiTones(float pitch) 57 | { 58 | setPitchSemiTones(handle, pitch); 59 | } 60 | 61 | 62 | public void setSpeed(float speed) 63 | { 64 | setSpeed(handle, speed); 65 | } 66 | 67 | 68 | public int processFile(String inputFile, String outputFile) 69 | { 70 | return processFile(handle, inputFile, outputFile); 71 | } 72 | 73 | 74 | // Load the native library upon startup 75 | static 76 | { 77 | System.loadLibrary("soundtouch"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/SPUtil.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice 2 | 3 | import android.content.Context.MODE_PRIVATE 4 | import android.content.SharedPreferences 5 | 6 | 7 | /** 8 | * @author DeMon 9 | * Created on 2021/4/29. 10 | * E-mail 757454343@qq.com 11 | * Desc: 12 | */ 13 | object SPUtil { 14 | 15 | private var mPref: SharedPreferences? = null 16 | 17 | init { 18 | mPref = App.mContext.getSharedPreferences(this.javaClass.simpleName, MODE_PRIVATE) 19 | } 20 | 21 | 22 | /** 23 | * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 24 | * 25 | * @param key 26 | * @param data 27 | */ 28 | fun save(key: String?, data: Any) { 29 | val editor = mPref?.edit() 30 | when (data) { 31 | is String -> { 32 | editor?.putString(key, data) 33 | } 34 | is Int -> { 35 | editor?.putInt(key, data) 36 | } 37 | is Boolean -> { 38 | editor?.putBoolean(key, data) 39 | } 40 | is Float -> { 41 | editor?.putFloat(key, data) 42 | } 43 | is Long -> { 44 | editor?.putLong(key, data) 45 | } 46 | else -> { 47 | editor?.putString(key, data.toString()) 48 | } 49 | } 50 | editor?.apply() 51 | } 52 | 53 | /** 54 | * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 55 | * 56 | * @param key 57 | * @param defaultObject 58 | * @return 59 | */ 60 | fun get(key: String?, defaultObject: Any): Any { 61 | return when (defaultObject) { 62 | is String -> { 63 | mPref?.getString(key, defaultObject) ?: "" 64 | } 65 | is Int -> { 66 | mPref?.getInt(key, defaultObject) ?: 0 67 | } 68 | is Boolean -> { 69 | mPref?.getBoolean(key, defaultObject) ?: false 70 | } 71 | is Float -> { 72 | mPref?.getFloat(key, defaultObject) ?: 0f 73 | } 74 | is Long -> { 75 | mPref?.getLong(key, defaultObject) ?: 0L 76 | } 77 | else -> "" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /SoundCoding/src/main/java/com/demon/soundcoding/WaveHeader.java: -------------------------------------------------------------------------------- 1 | package com.demon.soundcoding; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | 6 | /** 7 | * wav文件头 8 | */ 9 | public class WaveHeader { 10 | public final char fileID[] = {'R', 'I', 'F', 'F'}; 11 | public int fileLength; 12 | public char wavTag[] = {'W', 'A', 'V', 'E'}; 13 | ; 14 | public char FmtHdrID[] = {'f', 'm', 't', ' '}; 15 | public int FmtHdrLeth; 16 | public short FormatTag; 17 | public short Channels; 18 | public int SamplesPerSec; 19 | public int AvgBytesPerSec; 20 | public short BlockAlign; 21 | public short BitsPerSample; 22 | public char DataHdrID[] = {'d', 'a', 't', 'a'}; 23 | public int DataHdrLeth; 24 | 25 | public byte[] getHeader() throws IOException { 26 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 27 | WriteChar(bos, fileID); 28 | WriteInt(bos, fileLength); 29 | WriteChar(bos, wavTag); 30 | WriteChar(bos, FmtHdrID); 31 | WriteInt(bos, FmtHdrLeth); 32 | WriteShort(bos, FormatTag); 33 | WriteShort(bos, Channels); 34 | WriteInt(bos, SamplesPerSec); 35 | WriteInt(bos, AvgBytesPerSec); 36 | WriteShort(bos, BlockAlign); 37 | WriteShort(bos, BitsPerSample); 38 | WriteChar(bos, DataHdrID); 39 | WriteInt(bos, DataHdrLeth); 40 | bos.flush(); 41 | byte[] r = bos.toByteArray(); 42 | bos.close(); 43 | return r; 44 | } 45 | 46 | private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException { 47 | byte[] mybyte = new byte[2]; 48 | mybyte[1] = (byte) ((s << 16) >> 24); 49 | mybyte[0] = (byte) ((s << 24) >> 24); 50 | bos.write(mybyte); 51 | } 52 | 53 | private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException { 54 | byte[] buf = new byte[4]; 55 | buf[3] = (byte) (n >> 24); 56 | buf[2] = (byte) ((n << 8) >> 24); 57 | buf[1] = (byte) ((n << 16) >> 24); 58 | buf[0] = (byte) ((n << 24) >> 24); 59 | bos.write(buf); 60 | } 61 | 62 | private void WriteChar(ByteArrayOutputStream bos, char[] id) { 63 | for (int i = 0; i < id.length; i++) { 64 | char c = id[i]; 65 | bos.write(c); 66 | } 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/record/AudioRecordAmr.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice.record 2 | 3 | import android.media.MediaRecorder 4 | import android.os.Handler 5 | import android.util.Log 6 | import java.io.File 7 | 8 | /** 9 | * @author DeMon 10 | * Created on 2021/4/29. 11 | * E-mail 757454343@qq.com 12 | * Desc: 录制AMR 13 | */ 14 | class AudioRecordAmr : IAudioRecord { 15 | private val TAG = "AudioRecordAmr" 16 | private var mFilePath: String = "" 17 | private var mRecorder: MediaRecorder? = null 18 | private var listener: IAudioRecord.RecordListener? = null 19 | var handler: Handler = Handler() 20 | 21 | private var runnable: Runnable = object : Runnable { 22 | override fun run() { 23 | mRecorder?.run { 24 | //获取音量大小 25 | Log.i(TAG, "run: 音量:$maxAmplitude") 26 | listener?.volume(maxAmplitude) 27 | //声音波形计算 28 | //val db = 30 * log10((maxAmplitude / 100).toDouble()) 29 | } 30 | handler.postDelayed(this, 200) 31 | } 32 | } 33 | 34 | init { 35 | if (mRecorder == null) { 36 | mRecorder = MediaRecorder().apply { 37 | setAudioSource(MediaRecorder.AudioSource.MIC) 38 | setOutputFormat(MediaRecorder.OutputFormat.AMR_NB) 39 | setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) 40 | setAudioSamplingRate(8000) 41 | } 42 | } 43 | } 44 | 45 | override fun setRecordListener(listener: IAudioRecord.RecordListener?) { 46 | this.listener = listener 47 | } 48 | 49 | override fun startRecording(outFile: String) { 50 | mFilePath = outFile 51 | mRecorder?.run { 52 | listener?.start() 53 | val file = File(mFilePath) 54 | if (!file.exists()) { 55 | if (!file.parentFile.exists()) { 56 | file.parentFile.mkdirs() 57 | } 58 | file.createNewFile() 59 | } 60 | setOutputFile(mFilePath) 61 | prepare() 62 | start() 63 | } 64 | } 65 | 66 | override fun stopRecording() { 67 | listener?.stop(mFilePath) 68 | mRecorder?.stop() 69 | mRecorder?.release() 70 | mRecorder = null 71 | } 72 | 73 | override fun getOutFilePath(): String = mFilePath 74 | } -------------------------------------------------------------------------------- /FmodSound/src/main/java/com/demon/fmodsound/FmodSound.kt: -------------------------------------------------------------------------------- 1 | package com.demon.fmodsound 2 | 3 | import java.lang.Exception 4 | 5 | /** 6 | * @author DeMon 7 | * Created on 2020/12/31. 8 | * E-mail 757454343@qq.com 9 | * Desc: 10 | */ 11 | object FmodSound { 12 | //音效的类型 13 | const val MODE_NORMAL = 0 //正常 14 | 15 | const val MODE_FUNNY = 1 //搞笑 16 | 17 | const val MODE_UNCLE = 2 //大叔 18 | 19 | const val MODE_LOLITA = 3 //萝莉 20 | 21 | const val MODE_ROBOT = 4 //机器人 22 | 23 | const val MODE_ETHEREAL = 5 //空灵 24 | 25 | const val MODE_CHORUS = 6 //混合 26 | 27 | const val MODE_HORROR = 7 //恐怖 28 | 29 | init { 30 | System.loadLibrary("fmodL") 31 | System.loadLibrary("fmod") 32 | System.loadLibrary("FmodSound") 33 | } 34 | 35 | 36 | /** 37 | * 变声并播放,耗时,需要在子线程中执行 38 | * 39 | * @param path 音频路径,只支持WAV格式 40 | * @param type 变声音效类型,默认=0即普通播放无变声效果 41 | */ 42 | external fun playSound(path: String, type: Int = MODE_NORMAL): Int 43 | 44 | external fun stopPlay() 45 | external fun resumePlay() 46 | external fun pausePlay() 47 | external fun isPlaying(): Boolean 48 | 49 | /** 50 | * 变声保存,耗时,需要在子线程中执行 51 | * 52 | * @param path 音频路径,只支持WAV格式 53 | * @param type 变声音效类型,默认=0即普通播放无变声效果 54 | * @param savePath 变声后保存的路径,输出为WAV格式 55 | */ 56 | external fun saveSound(path: String, type: Int, savePath: String): Int 57 | 58 | /** 59 | * 变声保存 60 | * @param path 音频路径,只支持WAV格式 61 | * @param type 变声音效类型,默认=0即普通播放无变声效果 62 | * @param savePath 变声后保存的路径,输出为WAV格式 63 | * @param listener 变声结果监听,根据回调可以在变声成功后播放 64 | */ 65 | fun saveSoundAsync(path: String, type: Int, savePath: String, listener: ISaveSoundListener? = null) { 66 | try { 67 | if (isPlaying()) { 68 | stopPlay() 69 | } 70 | val result = saveSound(path, type, savePath) 71 | if (result == 0) { 72 | listener?.onFinish(path, savePath, type) 73 | } else { 74 | listener?.onError("error") 75 | } 76 | } catch (e: Exception) { 77 | listener?.onError(e.message) 78 | } 79 | } 80 | 81 | interface ISaveSoundListener { 82 | //成功 83 | fun onFinish(path: String, savePath: String, type: Int) 84 | 85 | //出错 86 | fun onError(msg: String?) 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 25 | 26 | 32 | 33 | 37 | 38 | 47 | 48 | 56 | 57 | 58 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/SoundFmodActivity.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice 2 | 3 | import android.os.Bundle 4 | import android.os.Environment 5 | import android.util.Log 6 | import android.widget.Toast 7 | import androidx.appcompat.app.AppCompatActivity 8 | import com.demon.changevoice.databinding.ActivitySoundFmodBinding 9 | import com.demon.changevoice.widget.FNRadioGroup 10 | import com.demon.fmodsound.FmodSound 11 | import com.demon.soundcoding.AmrToPcm 12 | import com.demon.soundcoding.AmrToWav 13 | import com.demon.soundcoding.WavToPcm 14 | import kotlinx.coroutines.GlobalScope 15 | import org.fmod.FMOD 16 | import java.io.File 17 | 18 | class SoundFmodActivity : AppCompatActivity() { 19 | private val TAG = "MainActivity" 20 | private lateinit var binding: ActivitySoundFmodBinding 21 | 22 | private var type: Int = FmodSound.MODE_NORMAL 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | //初始化 27 | FMOD.init(this) 28 | binding = ActivitySoundFmodBinding.inflate(layoutInflater) 29 | setContentView(binding.root) 30 | var path = intent.getStringExtra("path") ?: "" 31 | val file = File(path) 32 | if (!file.exists()) { 33 | showToast("录音文件不存在,请重新录制!") 34 | finish() 35 | } else { 36 | if (path.endsWith(".amr")) { 37 | path = AmrToWav.makeAmrToWav(path, false) 38 | } 39 | binding.tvPath.text = "音频文件:$path" 40 | } 41 | 42 | binding.fnRG.setOnCheckedChangeListener { group, checkedId -> 43 | val pos = group.indexOfChild(group.findViewById(checkedId)) 44 | Log.i(TAG, "onCreate: $pos") 45 | type = pos 46 | } 47 | binding.btnPlay.setOnClickListener { 48 | GlobalScope.launchIO { 49 | FmodSound.playSound(path, type) 50 | } 51 | } 52 | binding.btnSave.setOnClickListener { 53 | binding.tvSave.text = "开始变声..." 54 | GlobalScope.launchIO { 55 | FmodSound.saveSoundAsync(path, type, getRecordFilePath(1), object : FmodSound.ISaveSoundListener { 56 | override fun onFinish(path: String, savePath: String, type: Int) { 57 | runOnUiThread { 58 | binding.tvSave.text = "变声输出文件路径:$savePath" 59 | } 60 | FmodSound.playSound(savePath) 61 | } 62 | 63 | override fun onError(msg: String?) { 64 | Log.e(TAG, "onError: $msg") 65 | runOnUiThread { 66 | binding.tvSave.text = "变声失败:$msg" 67 | } 68 | } 69 | }) 70 | } 71 | } 72 | } 73 | 74 | 75 | override fun onDestroy() { 76 | super.onDestroy() 77 | //释放 78 | FMOD.close() 79 | } 80 | } -------------------------------------------------------------------------------- /SoundCoding/src/main/java/com/demon/soundcoding/WavToAmr.java: -------------------------------------------------------------------------------- 1 | package com.demon.soundcoding; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.InputStream; 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * @author DeMon 14 | * Created on 2021/4/28. 15 | * E-mail 757454343@qq.com 16 | * Desc: 17 | */ 18 | public class WavToAmr { 19 | private static final String TAG = "WavToAmr"; 20 | final private static byte[] header = new byte[]{0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A}; 21 | 22 | /** 23 | * WAV转AMR转,默认使用wav所在文件夹及文件名 24 | * 25 | * @param wavPath wav文件路径 26 | * @param isDeleteWav 转码成功后是否删除wav 27 | * @return 转码后的amr路径 28 | */ 29 | public static String makeWavToAmr(String wavPath, boolean isDeleteWav) { 30 | //建立输出文件 31 | String amrPath = ""; 32 | if (amrPath.endsWith(".wav")) { 33 | amrPath = amrPath.replace(".wav", ".amr"); 34 | } else { 35 | amrPath = amrPath + ".amr"; 36 | } 37 | return makeWavToAmr(wavPath, amrPath, isDeleteWav); 38 | } 39 | 40 | /** 41 | * 通过反射调用android系统自身AmrInputStream类进行转换 42 | * 43 | * @param wavPath wav源文件 44 | * @param amrPath amr目标文件 45 | */ 46 | public static String makeWavToAmr(String wavPath, String amrPath, boolean isDeleteWav) { 47 | try { 48 | Log.i(TAG, "makeAmrToPcm: wavPath=" + wavPath + ",amrPath=" + amrPath + ",isDeleteWav=" + isDeleteWav); 49 | File wavFile = new File(wavPath); 50 | if (!wavFile.exists()) { 51 | return ""; 52 | } 53 | FileInputStream fileInputStream = new FileInputStream(wavFile); 54 | FileOutputStream fileoutputStream = new FileOutputStream(amrPath); 55 | // 获得Class 56 | Class cls = Class.forName("android.media.AmrInputStream"); 57 | // 通过Class获得所对应对象的方法 58 | Method[] methods = cls.getMethods(); 59 | // 输出每个方法名 60 | fileoutputStream.write(header); 61 | Constructor con = cls.getConstructor(InputStream.class); 62 | Object obj = con.newInstance(fileInputStream); 63 | for (Method method : methods) { 64 | Class[] parameterTypes = method.getParameterTypes(); 65 | if ("read".equals(method.getName()) 66 | && parameterTypes.length == 3) { 67 | byte[] buf = new byte[1024]; 68 | int len = 0; 69 | while ((len = (int) method.invoke(obj, buf, 0, 1024)) > 0) { 70 | fileoutputStream.write(buf, 0, len); 71 | } 72 | break; 73 | } 74 | } 75 | for (Method method : methods) { 76 | if ("close".equals(method.getName())) { 77 | method.invoke(obj); 78 | break; 79 | } 80 | } 81 | fileoutputStream.close(); 82 | if (isDeleteWav) { 83 | wavFile.delete(); 84 | } 85 | Log.i(TAG, "makeWavToAmr: end!"); 86 | return amrPath; 87 | } catch (Exception e) { 88 | Log.e(TAG, "makeWavToAmr: ", e); 89 | e.printStackTrace(); 90 | return ""; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/record/AudioRecordWav.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice.record 2 | 3 | import android.media.AudioFormat 4 | import android.media.AudioRecord 5 | import android.media.MediaRecorder 6 | import android.util.Log 7 | import com.demon.changevoice.App 8 | import com.demon.changevoice.getRecordFilePath 9 | import com.demon.changevoice.launchIO 10 | import com.demon.soundcoding.PcmToWav 11 | import kotlinx.coroutines.GlobalScope 12 | import java.io.FileOutputStream 13 | 14 | /** 15 | * @author DeMon 16 | * Created on 2021/4/29. 17 | * E-mail 757454343@qq.com 18 | * Desc: 录制WAV 19 | */ 20 | class AudioRecordWav : IAudioRecord { 21 | private val TAG = "AudioRecordPcm" 22 | 23 | private var audioRecord: AudioRecord? = null 24 | private var listener: IAudioRecord.RecordListener? = null 25 | 26 | //音频输入-麦克风 27 | private val AUDIO_INPUT = MediaRecorder.AudioSource.MIC 28 | 29 | //采用频率 30 | //44100是目前的标准,但是某些设备仍然支持22050,16000,11025 31 | //采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级 32 | private val AUDIO_SAMPLE_RATE = 8000 33 | 34 | //声道 单声道 35 | private val AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO 36 | 37 | //编码 38 | private val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT 39 | 40 | protected var wavFileName: String = "" 41 | protected var pcmFileName: String = "" 42 | 43 | // 缓冲区字节大小 44 | private var bufferSizeInBytes = 0 45 | 46 | init { 47 | bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING) 48 | if (audioRecord == null) { 49 | audioRecord = AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes) 50 | } 51 | } 52 | 53 | override fun setRecordListener(listener: IAudioRecord.RecordListener?) { 54 | this.listener = listener 55 | } 56 | 57 | override fun startRecording(outFile: String) { 58 | wavFileName = outFile 59 | pcmFileName = App.mContext.getRecordFilePath(2) 60 | Log.i(TAG, "startRecording: ${audioRecord?.state}") 61 | if (audioRecord?.state == AudioRecord.STATE_INITIALIZED) { 62 | listener?.start() 63 | audioRecord?.startRecording() 64 | GlobalScope.launchIO { 65 | writeDataTOFile() 66 | } 67 | } else { 68 | Log.i(TAG, "startRecording: state error") 69 | } 70 | } 71 | 72 | override fun stopRecording() { 73 | GlobalScope.launchIO { 74 | wavFileName = PcmToWav.makePcmToWav(pcmFileName, wavFileName,true) 75 | listener?.stop(wavFileName) 76 | } 77 | audioRecord?.stop() 78 | audioRecord?.release() 79 | audioRecord = null 80 | } 81 | 82 | override fun getOutFilePath(): String = wavFileName 83 | 84 | 85 | private fun writeDataTOFile() { 86 | runCatching { 87 | // new一个byte数组用来存一些字节数据,大小为缓冲区大小 88 | val audiodata = ByteArray(bufferSizeInBytes) 89 | val fos = FileOutputStream(pcmFileName, false) 90 | audioRecord?.run { 91 | Log.i(TAG, "writeDataTOFile: $state") 92 | //将录音状态设置成正在录音状态 93 | while (state == AudioRecord.STATE_INITIALIZED) { 94 | val readsize = read(audiodata, 0, bufferSizeInBytes) 95 | if (AudioRecord.ERROR_INVALID_OPERATION != readsize) { 96 | var sum = 0 97 | for (i in 0 until readsize) { 98 | sum += kotlin.math.abs(audiodata[i].toInt()) 99 | } 100 | if (readsize > 0) { 101 | val raw = sum / readsize 102 | val lastVolumn = if (raw > 32) raw - 32 else 0 103 | listener?.volume(lastVolumn) 104 | } 105 | if (readsize > 0 && readsize <= audiodata.size) { 106 | fos.write(audiodata, 0, readsize) 107 | } 108 | } 109 | } 110 | fos.close() 111 | } 112 | }.onFailure { 113 | Log.e(TAG, "writeDataTOFile: ", it) 114 | } 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /app/src/main/java/com/demon/changevoice/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.demon.changevoice 2 | 3 | 4 | import android.Manifest 5 | import android.content.Intent 6 | import android.content.SharedPreferences 7 | import android.os.Bundle 8 | import android.os.Environment 9 | import android.widget.RadioButton 10 | import android.widget.Toast 11 | import androidx.appcompat.app.AppCompatActivity 12 | import com.demon.changevoice.databinding.ActivityMainBinding 13 | import com.demon.changevoice.record.PlayDialogFragment 14 | import com.demon.changevoice.record.RecordingDialogFragment 15 | import com.permissionx.guolindev.PermissionX 16 | import java.io.File 17 | 18 | class MainActivity : AppCompatActivity() { 19 | private val TAG = "MainActivity" 20 | private lateinit var binding: ActivityMainBinding 21 | 22 | private var allGranted: Boolean = false 23 | 24 | private var recordType = 0 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | initPermission() 28 | binding = ActivityMainBinding.inflate(layoutInflater) 29 | setContentView(binding.root) 30 | 31 | binding.rgGroup.setOnCheckedChangeListener { group, checkedId -> 32 | val rb = group.findViewById(checkedId) 33 | recordType = group.indexOfChild(rb) 34 | deleteRecord() 35 | } 36 | 37 | val savePath = SPUtil.get("record_path", "") as String; 38 | if (File(savePath).exists()) { 39 | binding.tvPath.text = savePath 40 | } 41 | binding.btnRecord.setOnClickListener { 42 | if (!allGranted) { 43 | showToast("Permission is missing!") 44 | } 45 | deleteRecord() 46 | RecordingDialogFragment.newInstance(recordType).apply { 47 | setListener(object : RecordingDialogFragment.Listener { 48 | override fun onCancel() { 49 | 50 | } 51 | 52 | override fun onFinish(path: String?) { 53 | binding.tvPath.text = path 54 | SPUtil.save("record_path", path ?: "") 55 | } 56 | }) 57 | showAllowingStateLoss(supportFragmentManager) 58 | } 59 | } 60 | 61 | binding.btnPlay.setOnClickListener { 62 | val path = binding.tvPath.text.toString() 63 | if (path.isEmpty()) { 64 | showToast("请先录音!") 65 | } else { 66 | PlayDialogFragment.newInstance(path).showAllowingStateLoss(supportFragmentManager) 67 | } 68 | } 69 | 70 | binding.bntFmod.setOnClickListener { 71 | val path = binding.tvPath.text.toString() 72 | if (path.isEmpty()) { 73 | showToast("请先录音!") 74 | } else { 75 | startActivity(Intent(this, SoundFmodActivity::class.java).putExtra("path", path)) 76 | } 77 | } 78 | binding.bntSoundTouch.setOnClickListener { 79 | val path = binding.tvPath.text.toString() 80 | if (path.isEmpty()) { 81 | showToast("请先录音!") 82 | } else { 83 | startActivity(Intent(this, SoundTouchActivity::class.java).putExtra("path", path)) 84 | } 85 | } 86 | } 87 | 88 | override fun onDestroy() { 89 | super.onDestroy() 90 | deleteRecord() 91 | } 92 | 93 | //删除上一个录音文件 94 | private fun deleteRecord() { 95 | val file = File(binding.tvPath.text.toString()) 96 | if (file.exists()) { 97 | file.delete() 98 | } 99 | binding.tvPath.text = "" 100 | } 101 | 102 | 103 | private fun initPermission() { 104 | PermissionX.init(this) 105 | .permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO) 106 | .request { allGranted, grantedList, deniedList -> 107 | this.allGranted = allGranted 108 | if (allGranted) { 109 | showToast("Permissions all Granted!") 110 | } else { 111 | showToast("These permissions are denied: $deniedList") 112 | } 113 | } 114 | } 115 | 116 | 117 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sound_touch.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 28 | 29 | 39 | 40 | 51 | 52 | 62 | 63 | 74 | 75 | 83 | 84 |