├── 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 |
4 |
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 |
8 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
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/#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 | 
48 | 
49 | 
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | xmlns:android
20 |
21 | ^$
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | xmlns:.*
31 |
32 | ^$
33 |
34 |
35 | BY_NAME
36 |
37 |
38 |
39 |
40 |
41 |
42 | .*:id
43 |
44 | http://schemas.android.com/apk/res/android
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | .*:name
54 |
55 | http://schemas.android.com/apk/res/android
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | name
65 |
66 | ^$
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | style
76 |
77 | ^$
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | .*
87 |
88 | ^$
89 |
90 |
91 | BY_NAME
92 |
93 |
94 |
95 |
96 |
97 |
98 | .*
99 |
100 | http://schemas.android.com/apk/res/android
101 |
102 |
103 | ANDROID_ATTRIBUTE_ORDER
104 |
105 |
106 |
107 |
108 |
109 |
110 | .*
111 |
112 | .*
113 |
114 |
115 | BY_NAME
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
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 |
94 |
95 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demon/changevoice/SoundTouchActivity.kt:
--------------------------------------------------------------------------------
1 | package com.demon.changevoice
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import android.widget.SeekBar
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.demon.changevoice.databinding.ActivitySoundTouchBinding
8 | import com.demon.changevoice.record.PlayDialogFragment
9 | import com.demon.soundcoding.AmrToWav
10 | import kotlinx.coroutines.GlobalScope
11 | import net.surina.soundtouch.SoundTouch
12 | import java.io.File
13 |
14 | /**
15 | * SoundTouch变声只支持.WAV
16 | */
17 | class SoundTouchActivity : AppCompatActivity() {
18 | private val TAG = "SoundTouchActivity"
19 | private lateinit var binding: ActivitySoundTouchBinding
20 |
21 | private var pitch = 0.0f
22 | private var tempo = 1.0f
23 | private var speed = 1.0f
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | binding = ActivitySoundTouchBinding.inflate(layoutInflater)
27 | setContentView(binding.root)
28 | var path = intent.getStringExtra("path") ?: ""
29 | Log.i(TAG, "onCreate: $path")
30 | val file = File(path)
31 | if (!file.exists()) {
32 | showToast("录音文件不存在,请重新录制!")
33 | finish()
34 | } else {
35 | if (path.endsWith(".amr")) {
36 | path = AmrToWav.makeAmrToWav(path, false)
37 | }
38 | binding.tvPath.text = "音频文件:$path"
39 | }
40 | binding.seekPitch.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
41 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
42 | pitch = (progress - 12).toFloat()
43 | binding.textPitch.text = "音调(-12~12)---当前:$pitch"
44 | }
45 |
46 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
47 |
48 | }
49 |
50 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
51 |
52 | }
53 | })
54 | binding.seekTempo.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
55 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
56 | tempo = progress.toFloat() / 10
57 | binding.textTempo.text = "速度(0~10)---当前:$tempo"
58 | }
59 |
60 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
61 |
62 | }
63 |
64 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
65 |
66 | }
67 | })
68 | binding.seekSpeed.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
69 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
70 | speed = progress.toFloat() / 10
71 | binding.textSpeed.text = "音调&速度(0~10)---当前:$speed"
72 | }
73 |
74 | override fun onStartTrackingTouch(seekBar: SeekBar?) {
75 |
76 | }
77 |
78 | override fun onStopTrackingTouch(seekBar: SeekBar?) {
79 |
80 | }
81 | })
82 | binding.buttonProcess.setOnClickListener {
83 | GlobalScope.launchIO {
84 | try {
85 | process(path, getRecordFilePath(1))
86 | } catch (e: Exception) {
87 | e.printStackTrace()
88 | }
89 | }
90 | }
91 | }
92 |
93 | /**
94 | * 执行变声,耗时,需要在子线程中执行
95 | *
96 | * @param path 音频文件
97 | * @param savePath 变声后文件保存路径
98 | */
99 | private fun process(path: String, savePath: String) {
100 | try {
101 | val st = SoundTouch()
102 | st.setTempo(tempo) //速度
103 | st.setSpeed(speed) //速度&音调
104 | st.setPitchSemiTones(pitch) //音调
105 | val res = st.processFile(path, savePath)
106 | GlobalScope.launchUI {
107 | if (res == 0) {
108 | binding.tvChange.text = "变声输出文件路径:$savePath"
109 | PlayDialogFragment.newInstance(savePath).showAllowingStateLoss(supportFragmentManager)
110 | } else {
111 | showToast(SoundTouch.getErrorString())
112 | }
113 | }
114 | } catch (e: Exception) {
115 | e.printStackTrace()
116 |
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/demon/changevoice/record/RecordingDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.demon.changevoice.record
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.os.SystemClock
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.DialogFragment
10 | import androidx.fragment.app.FragmentManager
11 | import com.demon.changevoice.R
12 | import com.demon.changevoice.databinding.FragmentDialogRecordingBinding
13 | import com.demon.changevoice.getRecordFilePath
14 | import com.demon.changevoice.showToast
15 | import java.io.File
16 | import java.text.SimpleDateFormat
17 | import java.util.*
18 |
19 | /**
20 | * 开始录音的 DialogFragment
21 | *
22 | *
23 | * Created by DeMon on 2018/7/19.
24 | */
25 | class RecordingDialogFragment : DialogFragment() {
26 | private var _binding: FragmentDialogRecordingBinding? = null
27 |
28 | private val binding
29 | get() = _binding!!
30 |
31 | private var isRecording = false
32 | private var intent: Intent? = null
33 | private var mListener: Listener? = null
34 | private var filePath: String = ""
35 |
36 | override fun onCreate(savedInstanceState: Bundle?) {
37 | super.onCreate(savedInstanceState)
38 | setStyle(STYLE_NORMAL, R.style.CustomBottomSheetStyle)
39 | }
40 |
41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
42 | _binding = FragmentDialogRecordingBinding.bind(inflater.inflate(R.layout.fragment_dialog_recording, container, false))
43 | return _binding?.root
44 | }
45 |
46 |
47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 | super.onViewCreated(view, savedInstanceState)
49 | binding.recordAudioFabRecord.setOnClickListener {
50 | isRecording = !isRecording
51 | onRecord(isRecording)
52 | }
53 | binding.recordAudioIvClose.setOnClickListener {
54 | if (isRecording) {
55 | showToast("录音取消...")
56 | mListener?.onCancel()
57 | }
58 | dismissAllowingStateLoss()
59 | }
60 | }
61 |
62 |
63 | private fun onRecord(flag: Boolean) {
64 | intent = Intent(activity, RecordingService::class.java)
65 | if (flag) {
66 | val type = arguments?.getInt("type", 0) ?: 0
67 | filePath = getRecordFilePath(type)
68 | intent?.putExtra("FilePath", filePath)
69 | intent?.putExtra("Type", type)
70 | binding.recordAudioFabRecord.setImageResource(R.drawable.base_recording_stop)
71 | showToast("开始录音...")
72 | binding.recordAudioChronometerTime.base = SystemClock.elapsedRealtime()
73 | binding.recordAudioChronometerTime.start()
74 | requireActivity().startService(intent)
75 | } else {
76 | binding.recordAudioFabRecord.setImageResource(R.drawable.base_recording_start)
77 | binding.recordAudioChronometerTime.stop()
78 | mListener?.onFinish(filePath)
79 | showToast("录音结束...")
80 | activity?.stopService(intent)
81 | dismissAllowingStateLoss()
82 | }
83 | }
84 |
85 | fun showAllowingStateLoss(manager: FragmentManager) {
86 | if (!isVisible) {
87 | manager.beginTransaction().add(this, tag).commitAllowingStateLoss()
88 | }
89 | }
90 |
91 | override fun onDestroyView() {
92 | super.onDestroyView()
93 | if (isRecording) {
94 | val file = File(filePath)
95 | if (file.exists()) {
96 | file.delete()
97 | }
98 | requireActivity().stopService(intent)
99 | }
100 | _binding = null
101 | }
102 |
103 | interface Listener {
104 | fun onCancel()
105 | fun onFinish(path: String?)
106 | }
107 |
108 | fun setListener(listener: Listener?) {
109 | mListener = listener
110 | }
111 |
112 | companion object {
113 | private const val TAG = "RecordAudioDialogFragme"
114 | fun newInstance(type: Int): RecordingDialogFragment {
115 | val fragment = RecordingDialogFragment()
116 | val bundle = Bundle()
117 | bundle.putInt("type", type)
118 | fragment.arguments = bundle
119 | return fragment
120 | }
121 |
122 | }
123 | }
--------------------------------------------------------------------------------
/FmodSound/src/main/cpp/inc/fmod_codec.h:
--------------------------------------------------------------------------------
1 | /* ======================================================================================== */
2 | /* FMOD Core API - Codec development header file. */
3 | /* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2020. */
4 | /* */
5 | /* Use this header if you are wanting to develop your own file format plugin to use with */
6 | /* FMOD's codec system. With this header you can make your own fileformat plugin that FMOD */
7 | /* can register and use. See the documentation and examples on how to make a working */
8 | /* plugin. */
9 | /* */
10 | /* For more detail visit: */
11 | /* https://fmod.com/resources/documentation-api?version=2.0&page=core-api.html */
12 | /* ======================================================================================== */
13 | #ifndef _FMOD_CODEC_H
14 | #define _FMOD_CODEC_H
15 |
16 | /*
17 | Codec types
18 | */
19 | typedef struct FMOD_CODEC_STATE FMOD_CODEC_STATE;
20 | typedef struct FMOD_CODEC_WAVEFORMAT FMOD_CODEC_WAVEFORMAT;
21 |
22 | /*
23 | Codec constants
24 | */
25 | #define FMOD_CODEC_WAVEFORMAT_VERSION 3
26 |
27 | /*
28 | Codec callbacks
29 | */
30 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_OPEN_CALLBACK) (FMOD_CODEC_STATE *codec_state, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo);
31 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_CLOSE_CALLBACK) (FMOD_CODEC_STATE *codec_state);
32 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_READ_CALLBACK) (FMOD_CODEC_STATE *codec_state, void *buffer, unsigned int samples_in, unsigned int *samples_out);
33 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETLENGTH_CALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *length, FMOD_TIMEUNIT lengthtype);
34 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SETPOSITION_CALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, unsigned int position, FMOD_TIMEUNIT postype);
35 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETPOSITION_CALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *position, FMOD_TIMEUNIT postype);
36 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SOUNDCREATE_CALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, FMOD_SOUND *sound);
37 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETWAVEFORMAT_CALLBACK)(FMOD_CODEC_STATE *codec_state, int index, FMOD_CODEC_WAVEFORMAT *waveformat);
38 |
39 | /*
40 | Codec functions
41 | */
42 | typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_METADATA_FUNC) (FMOD_CODEC_STATE *codec_state, FMOD_TAGTYPE tagtype, char *name, void *data, unsigned int datalen, FMOD_TAGDATATYPE datatype, int unique);
43 |
44 | /*
45 | Codec structures
46 | */
47 | typedef struct FMOD_CODEC_DESCRIPTION
48 | {
49 | const char *name;
50 | unsigned int version;
51 | int defaultasstream;
52 | FMOD_TIMEUNIT timeunits;
53 | FMOD_CODEC_OPEN_CALLBACK open;
54 | FMOD_CODEC_CLOSE_CALLBACK close;
55 | FMOD_CODEC_READ_CALLBACK read;
56 | FMOD_CODEC_GETLENGTH_CALLBACK getlength;
57 | FMOD_CODEC_SETPOSITION_CALLBACK setposition;
58 | FMOD_CODEC_GETPOSITION_CALLBACK getposition;
59 | FMOD_CODEC_SOUNDCREATE_CALLBACK soundcreate;
60 | FMOD_CODEC_GETWAVEFORMAT_CALLBACK getwaveformat;
61 | } FMOD_CODEC_DESCRIPTION;
62 |
63 | struct FMOD_CODEC_WAVEFORMAT
64 | {
65 | const char* name;
66 | FMOD_SOUND_FORMAT format;
67 | int channels;
68 | int frequency;
69 | unsigned int lengthbytes;
70 | unsigned int lengthpcm;
71 | unsigned int pcmblocksize;
72 | int loopstart;
73 | int loopend;
74 | FMOD_MODE mode;
75 | FMOD_CHANNELMASK channelmask;
76 | FMOD_CHANNELORDER channelorder;
77 | float peakvolume;
78 | };
79 |
80 | struct FMOD_CODEC_STATE
81 | {
82 | int numsubsounds;
83 | FMOD_CODEC_WAVEFORMAT *waveformat;
84 | void *plugindata;
85 |
86 | void *filehandle;
87 | unsigned int filesize;
88 | FMOD_FILE_READ_CALLBACK fileread;
89 | FMOD_FILE_SEEK_CALLBACK fileseek;
90 | FMOD_CODEC_METADATA_FUNC metadata;
91 |
92 | int waveformatversion;
93 | };
94 |
95 | #endif
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_sound_fmod.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
29 |
30 |
36 |
37 |
43 |
44 |
50 |
51 |
57 |
58 |
64 |
65 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
96 |
97 |
106 |
107 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
26 |
27 |
33 |
34 |
39 |
40 |
41 |
50 |
51 |
61 |
62 |
70 |
71 |
78 |
79 |
89 |
90 |
101 |
102 |
115 |
--------------------------------------------------------------------------------
/SoundCoding/src/main/java/com/demon/soundcoding/PcmToAmr.java:
--------------------------------------------------------------------------------
1 | package com.demon.soundcoding;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaFormat;
5 | import android.util.Log;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileOutputStream;
10 | import java.nio.ByteBuffer;
11 |
12 | /**
13 | * PCM转AMR
14 | */
15 | public class PcmToAmr {
16 | private static final String TAG = "SoundCoding_PcmToAmr";
17 | private static int SAMPLE_RATE = 8000;
18 | private static int BIT_RATE = 64;
19 | private static byte[] header = new byte[]{'#', '!', 'A', 'M', 'R', '\n'};
20 |
21 | /**
22 | * PCM转AMR转,默认使用pcm所在文件夹及文件名
23 | *
24 | * @param pcmPath pcm文件路径
25 | * @param isDeletePcm 转码成功后是否删除pcm
26 | * @return 转码后的amr路径
27 | */
28 | public static String makePcmToAmr(String pcmPath, boolean isDeletePcm) {
29 | //建立输出文件
30 | String amrPath = "";
31 | if (amrPath.endsWith(".pcm")) {
32 | amrPath = amrPath.replace(".pcm", ".amr");
33 | } else {
34 | amrPath = amrPath + ".amr";
35 | }
36 | return makePcmToAmr(pcmPath, amrPath, isDeletePcm);
37 | }
38 |
39 | /**
40 | * PCM转AMR转
41 | *
42 | * @param pcmPath pcm文件路径
43 | * @param amrPath amr文件路径
44 | * @param isDeletePcm 转码成功后是否删除pcm
45 | * @return 转码后的amr路径
46 | */
47 | public static String makePcmToAmr(String pcmPath, String amrPath, boolean isDeletePcm) {
48 | try {
49 | Log.i(TAG, "makeAmrToPcm: pcmPath=" + pcmPath + ",amrPath=" + amrPath + ",isDeletePcm=" + isDeletePcm);
50 | MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
51 | MediaFormat format = new MediaFormat();
52 | format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
53 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
54 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
55 | format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
56 |
57 |
58 | encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
59 | File pcmFile = new File(pcmPath);
60 | FileInputStream fis = new FileInputStream(pcmFile);
61 | File armFIle = new File(amrPath);
62 | FileOutputStream fos = new FileOutputStream(armFIle);
63 | fos.write(header);
64 | encoder.start();
65 |
66 | ByteBuffer[] codecInputBuffers = encoder.getInputBuffers();
67 | ByteBuffer[] codecOutputBuffers = encoder.getOutputBuffers();
68 | byte[] tempBuffer = new byte[88200];
69 | boolean hasMoreData = true;
70 | MediaCodec.BufferInfo outBuffInfo = new MediaCodec.BufferInfo();
71 | double presentationTimeUs = 0;
72 | int totalBytesRead = 0;
73 | do {
74 | int inputBufIndex = 0;
75 | while (inputBufIndex != -1 && hasMoreData) {
76 | inputBufIndex = encoder.dequeueInputBuffer(1000);
77 | if (inputBufIndex >= 0) {
78 | ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
79 | dstBuf.clear();
80 | int bytesRead = fis.read(tempBuffer, 0, dstBuf.limit());
81 | if (bytesRead == -1) { // -1 implies EOS
82 | hasMoreData = false;
83 | encoder.queueInputBuffer(inputBufIndex, 0, 0, (long) presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
84 | } else {
85 | totalBytesRead += bytesRead;
86 | dstBuf.put(tempBuffer, 0, bytesRead);
87 | encoder.queueInputBuffer(inputBufIndex, 0, bytesRead, (long) presentationTimeUs, 0);
88 | presentationTimeUs = 1000000l * (totalBytesRead / 2) / SAMPLE_RATE;
89 | }
90 | }
91 | }
92 | // Drain audio
93 | int outputBufIndex = 0;
94 | while (outputBufIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
95 | outputBufIndex = encoder.dequeueOutputBuffer(outBuffInfo, -1);
96 | if (outputBufIndex >= 0) {
97 | ByteBuffer encodedData = codecOutputBuffers[outputBufIndex];
98 | encodedData.position(outBuffInfo.offset);
99 | encodedData.limit(outBuffInfo.offset + outBuffInfo.size);
100 | byte[] outData = new byte[outBuffInfo.size];
101 | encodedData.get(outData, 0, outBuffInfo.size);
102 | fos.write(outData, 0, outBuffInfo.size);
103 | encoder.releaseOutputBuffer(outputBufIndex, false);
104 | }
105 | }
106 | } while (outBuffInfo.flags != MediaCodec.BUFFER_FLAG_END_OF_STREAM);
107 | if (fis != null) {
108 | fis.close();
109 | fis = null;
110 | }
111 | if (fos != null) {
112 | fos.flush();
113 | fos.close();
114 | }
115 | if (isDeletePcm) {
116 | pcmFile.delete();
117 | }
118 | Log.i(TAG, "makePcmToAmr: end!");
119 | return armFIle.getAbsolutePath();
120 | } catch (Exception ef) {
121 | ef.printStackTrace();
122 | Log.e(TAG, "makePcmToAmr: ", ef);
123 | return "";
124 | }
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/SoundCoding/src/main/java/com/demon/soundcoding/PcmToWav.java:
--------------------------------------------------------------------------------
1 | package com.demon.soundcoding;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.BufferedInputStream;
6 | import java.io.BufferedOutputStream;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.OutputStream;
13 | import java.util.List;
14 |
15 | /**
16 | * 将pcm文件转化为wav文件
17 | */
18 | public class PcmToWav {
19 | private static final String TAG = "PcmToWav";
20 |
21 | /**
22 | * 将一个pcm文件转化为wav文件,默认使用pcm所在文件夹及文件名
23 | *
24 | * @param pcmPath pcm文件路径
25 | * @param deletePcmFile 是否删除源文件
26 | */
27 | public static String makePcmToWav(String pcmPath, boolean deletePcmFile) {
28 | //建立输出文件
29 | String wavPath = "";
30 | if (pcmPath.endsWith(".pcm")) {
31 | pcmPath = pcmPath.replace(".pcm", ".wav");
32 | } else {
33 | pcmPath = pcmPath + ".wav";
34 | }
35 | return makePcmToWav(pcmPath, wavPath, deletePcmFile);
36 | }
37 |
38 | /**
39 | * 将一个pcm文件转化为wav文件
40 | *
41 | * @param pcmPath pcm文件路径
42 | * @param wavPath 目标文件路径(wav)
43 | * @param isDeletePcm 是否删除源文件
44 | */
45 | public static String makePcmToWav(String pcmPath, String wavPath, boolean isDeletePcm) {
46 | try {
47 | Log.i(TAG, "makeAmrToPcm: pcmPath=" + pcmPath + ",wavPath=" + wavPath + ",isDeletePcm=" + isDeletePcm);
48 | File file = new File(pcmPath);
49 | if (!file.exists()) {
50 | return "";
51 | }
52 | int TOTAL_SIZE = (int) file.length();
53 | // 填入参数,比特率等等。这里用的是16位单声道 8000 hz
54 | WaveHeader header = new WaveHeader();
55 | // 长度字段 = 内容的大小(TOTAL_SIZE) +
56 | // 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
57 | header.fileLength = TOTAL_SIZE + (44 - 8);
58 | header.FmtHdrLeth = 16;
59 | header.BitsPerSample = 16;
60 | header.Channels = 1;
61 | header.FormatTag = 0x0001;
62 | header.SamplesPerSec = 8000;
63 | header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
64 | header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
65 | header.DataHdrLeth = TOTAL_SIZE;
66 | byte[] h = header.getHeader();
67 | if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件
68 | return "";
69 | //合成所有的pcm文件的数据,写到目标文件
70 | byte[] buffer = new byte[1024 * 4]; // Length of All Files, Total Size
71 | OutputStream ouStream = new BufferedOutputStream(new FileOutputStream(wavPath));
72 | ouStream.write(h, 0, h.length);
73 | InputStream inStream = new BufferedInputStream(new FileInputStream(file));
74 | int size = inStream.read(buffer);
75 | while (size != -1) {
76 | ouStream.write(buffer);
77 | size = inStream.read(buffer);
78 | }
79 | inStream.close();
80 | ouStream.close();
81 | if (isDeletePcm) {
82 | file.delete();
83 | }
84 | Log.i(TAG, "makePcmToWav success!");
85 | return wavPath;
86 | } catch (Exception e) {
87 | Log.e(TAG, "makePcmToWav: ", e);
88 | return "";
89 | }
90 | }
91 |
92 | /**
93 | * 合并多个pcm文件为一个wav文件
94 | *
95 | * @param filePathList pcm文件路径集合
96 | * @param wavPath 目标wav文件路径
97 | * @return true|false
98 | */
99 |
100 | public static boolean mergePcmsToWav(List filePathList, String wavPath) {
101 | File[] file = new File[filePathList.size()];
102 | byte buffer[] = null;
103 |
104 | int TOTAL_SIZE = 0;
105 | int fileNum = filePathList.size();
106 |
107 | for (int i = 0; i < fileNum; i++) {
108 | file[i] = new File(filePathList.get(i));
109 | TOTAL_SIZE += file[i].length();
110 | }
111 |
112 | // 填入参数,比特率等等。这里用的是16位单声道 8000 hz
113 | WaveHeader header = new WaveHeader();
114 | // 长度字段 = 内容的大小(TOTAL_SIZE) +
115 | // 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
116 | header.fileLength = TOTAL_SIZE + (44 - 8);
117 | header.FmtHdrLeth = 16;
118 | header.BitsPerSample = 16;
119 | header.Channels = 2;
120 | header.FormatTag = 0x0001;
121 | header.SamplesPerSec = 8000;
122 | header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
123 | header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
124 | header.DataHdrLeth = TOTAL_SIZE;
125 |
126 | byte[] h = null;
127 | try {
128 | h = header.getHeader();
129 | } catch (IOException e1) {
130 | Log.e(TAG, e1.getMessage());
131 | return false;
132 | }
133 |
134 | if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件
135 | return false;
136 |
137 | //先删除目标文件
138 | File destfile = new File(wavPath);
139 | if (destfile.exists())
140 | destfile.delete();
141 |
142 | //合成所有的pcm文件的数据,写到目标文件
143 | try {
144 | buffer = new byte[1024 * 4]; // Length of All Files, Total Size
145 | InputStream inStream = null;
146 | OutputStream ouStream = null;
147 |
148 | ouStream = new BufferedOutputStream(new FileOutputStream(
149 | wavPath));
150 | ouStream.write(h, 0, h.length);
151 | for (int j = 0; j < fileNum; j++) {
152 | inStream = new BufferedInputStream(new FileInputStream(file[j]));
153 | int size = inStream.read(buffer);
154 | while (size != -1) {
155 | ouStream.write(buffer);
156 | size = inStream.read(buffer);
157 | }
158 | inStream.close();
159 | }
160 | ouStream.close();
161 | } catch (IOException e) {
162 | Log.e(TAG, e.getMessage());
163 | return false;
164 | }
165 | // clearFiles(filePathList);
166 | Log.i(TAG, "mergePCMFilesToWAVFile success!");
167 | return true;
168 | }
169 |
170 | }
171 |
172 |
173 |
--------------------------------------------------------------------------------
/SoundCoding/src/main/java/com/demon/soundcoding/AmrToPcm.java:
--------------------------------------------------------------------------------
1 | package com.demon.soundcoding;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaExtractor;
5 | import android.media.MediaFormat;
6 | import android.text.TextUtils;
7 | import android.util.Log;
8 |
9 | import java.io.File;
10 | import java.io.FileOutputStream;
11 | import java.io.IOException;
12 | import java.nio.ByteBuffer;
13 |
14 | /**
15 | * AMR转PCM
16 | */
17 | public class AmrToPcm {
18 | private final static int SAMPLE_RATE = 8000;
19 | private final static int BIT_RATE = 64;
20 | private static final String TAG = "SoundCoding_AmrToPcm";
21 |
22 | /**
23 | * AMR转PCM,默认使用amr所在文件夹及文件名
24 | *
25 | * @param amrPath amr文件路径
26 | * @param isDeleteAmr 转码成功后是否删除amr文件
27 | * @return 转码后的pcm路径
28 | */
29 | public static String makeAmrToPcm(String amrPath, boolean isDeleteAmr) {
30 | //建立输出文件
31 | String pcmPath = "";
32 | if (amrPath.endsWith(".amr")) {
33 | pcmPath = amrPath.replace(".amr", ".pcm");
34 | } else {
35 | pcmPath = amrPath + ".pcm";
36 | }
37 |
38 | return makeAmrToPcm(amrPath, pcmPath, isDeleteAmr);
39 | }
40 |
41 | /**
42 | * AMR转PCM
43 | *
44 | * @param amrPath amr文件路径
45 | * @param pcmPath pcm文件路径
46 | * @param isDeleteAmr 转码成功后是否删除amr文件
47 | * @return 转码后的pcm路径
48 | */
49 | public static String makeAmrToPcm(String amrPath, String pcmPath, boolean isDeleteAmr) {
50 | try {
51 | Log.i(TAG, "makeAmrToPcm: amrPath=" + amrPath + ",pcmPath=" + pcmPath + ",isDeleteAmr=" + isDeleteAmr);
52 | //校验文件合法性
53 | File amrFile = new File(amrPath);
54 | if (!amrFile.exists()) {
55 | throw new IllegalArgumentException("amr file not found : " + amrFile.getAbsolutePath());
56 | }
57 | if (!isLegalFile(amrFile)) {
58 | throw new IllegalArgumentException("amr file is not a legal file : " + amrFile.getAbsolutePath());
59 | }
60 | if (TextUtils.isEmpty(pcmPath)) {
61 | throw new IllegalArgumentException("pcmPath can not empty!");
62 | }
63 | //建立输出文件
64 | File pcmFile = new File(pcmPath);
65 | FileOutputStream pcmOutPutStream = new FileOutputStream(pcmFile);
66 | //获取文件信息
67 | MediaExtractor mediaExtractor = new MediaExtractor();
68 | mediaExtractor.setDataSource(amrPath);
69 | for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
70 | MediaFormat format = mediaExtractor.getTrackFormat(i);
71 | if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
72 | mediaExtractor.selectTrack(i);
73 | break;
74 | }
75 | }
76 | //设置解码器
77 | MediaCodec decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
78 | MediaFormat format = new MediaFormat();
79 | format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
80 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
81 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
82 | format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
83 | decoder.configure(format, null, null, 0);
84 | decoder.start();
85 | //获取输入输出队列
86 | ByteBuffer[] inputBuffers = decoder.getInputBuffers();
87 | ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
88 | //获取当前缓冲信息
89 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
90 | for (; ; ) {
91 | int inIndex = decoder.dequeueInputBuffer(1000);
92 | if (inIndex >= 0) {
93 | //入队输入数据
94 | ByteBuffer buffer = inputBuffers[inIndex];
95 | int sampleSize = mediaExtractor.readSampleData(buffer, 0);
96 | if (sampleSize < 0) {
97 | Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
98 | decoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
99 | } else {
100 | decoder.queueInputBuffer(inIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0);
101 | mediaExtractor.advance();
102 | }
103 | //出队输出数据
104 | int outIndex = decoder.dequeueOutputBuffer(bufferInfo, -1);
105 | switch (outIndex) {
106 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: {
107 | Log.d(TAG, "INFO_OUT_PUT_BUFFERS_CHANGED");
108 | outputBuffers = decoder.getOutputBuffers();
109 | break;
110 | }
111 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: {
112 | MediaFormat mediaFormat = decoder.getOutputFormat();
113 | Log.d(TAG, "MediaFormat=" + mediaFormat);
114 | break;
115 | }
116 | case MediaCodec.INFO_TRY_AGAIN_LATER: {
117 | Log.d(TAG, "Decoding timeout");
118 | break;
119 | }
120 | default: {
121 | ByteBuffer outBuffer = outputBuffers[outIndex];
122 | final byte[] chunk = new byte[bufferInfo.size];
123 | outBuffer.get(chunk);
124 | outBuffer.clear();
125 | pcmOutPutStream.write(chunk, 0, chunk.length);
126 | decoder.releaseOutputBuffer(outIndex, false);
127 | break;
128 | }
129 | }
130 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
131 | Log.d(TAG, "End of parsing");
132 | break;
133 | }
134 | }
135 | }
136 | mediaExtractor.release();
137 | decoder.stop();
138 | decoder.release();
139 | if (isDeleteAmr) {
140 | amrFile.delete();
141 | }
142 | return pcmFile.getAbsolutePath();
143 | } catch (IOException e) {
144 | Log.e(TAG, "makeAmrToPcm: ", e);
145 | return "";
146 | }
147 | }
148 |
149 |
150 | public static final boolean isLegalFile(File file) {
151 | return file != null && file.exists() && file.canRead() && file.isFile() && file.length() > 0L;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/FmodSound/src/main/cpp/inc/fmod_output.h:
--------------------------------------------------------------------------------
1 | /* ======================================================================================== */
2 | /* FMOD Core API - output development header file. */
3 | /* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2020. */
4 | /* */
5 | /* Use this header if you are wanting to develop your own output plugin to use with */
6 | /* FMOD's output system. With this header you can make your own output plugin that FMOD */
7 | /* can register and use. See the documentation and examples on how to make a working */
8 | /* plugin. */
9 | /* */
10 | /* For more detail visit: */
11 | /* https://fmod.com/resources/documentation-api?version=2.0&page=plugin-api-output.html */
12 | /* ======================================================================================== */
13 | #ifndef _FMOD_OUTPUT_H
14 | #define _FMOD_OUTPUT_H
15 |
16 | typedef struct FMOD_OUTPUT_STATE FMOD_OUTPUT_STATE;
17 | typedef struct FMOD_OUTPUT_OBJECT3DINFO FMOD_OUTPUT_OBJECT3DINFO;
18 |
19 | /*
20 | Output constants
21 | */
22 | #define FMOD_OUTPUT_PLUGIN_VERSION 4
23 |
24 | typedef unsigned int FMOD_OUTPUT_METHOD;
25 | #define FMOD_OUTPUT_METHOD_MIX_DIRECT 0
26 | #define FMOD_OUTPUT_METHOD_POLLING 1
27 | #define FMOD_OUTPUT_METHOD_MIX_BUFFERED 2
28 |
29 | /*
30 | Output callbacks
31 | */
32 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_GETNUMDRIVERS_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int *numdrivers);
33 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_GETDRIVERINFO_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int id, char *name, int namelen, FMOD_GUID *guid, int *systemrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels);
34 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_INIT_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int selecteddriver, FMOD_INITFLAGS flags, int *outputrate, FMOD_SPEAKERMODE *speakermode, int *speakermodechannels, FMOD_SOUND_FORMAT *outputformat, int dspbufferlength, int dspnumbuffers, void *extradriverdata);
35 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_START_CALLBACK) (FMOD_OUTPUT_STATE *output_state);
36 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_STOP_CALLBACK) (FMOD_OUTPUT_STATE *output_state);
37 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_CLOSE_CALLBACK) (FMOD_OUTPUT_STATE *output_state);
38 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_UPDATE_CALLBACK) (FMOD_OUTPUT_STATE *output_state);
39 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_GETHANDLE_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void **handle);
40 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_GETPOSITION_CALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int *pcm);
41 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_LOCK_CALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2);
42 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_UNLOCK_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2);
43 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_MIXER_CALLBACK) (FMOD_OUTPUT_STATE *output_state);
44 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_OBJECT3DGETINFO_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int *maxhardwareobjects);
45 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_OBJECT3DALLOC_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void **object3d);
46 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_OBJECT3DFREE_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void *object3d);
47 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_OBJECT3DUPDATE_CALLBACK) (FMOD_OUTPUT_STATE *output_state, void *object3d, const FMOD_OUTPUT_OBJECT3DINFO *info);
48 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_OPENPORT_CALLBACK) (FMOD_OUTPUT_STATE *output_state, FMOD_PORT_TYPE portType, FMOD_PORT_INDEX portIndex, int *portId, int *portRate, int *portChannels, FMOD_SOUND_FORMAT *portFormat);
49 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_CLOSEPORT_CALLBACK) (FMOD_OUTPUT_STATE *output_state, int portId);
50 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_DEVICELISTCHANGED_CALLBACK)(FMOD_OUTPUT_STATE *output_state);
51 |
52 | /*
53 | Output functions
54 | */
55 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_READFROMMIXER_FUNC) (FMOD_OUTPUT_STATE *output_state, void *buffer, unsigned int length);
56 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_COPYPORT_FUNC) (FMOD_OUTPUT_STATE *output_state, int portId, void *buffer, unsigned int length);
57 | typedef FMOD_RESULT (F_CALL *FMOD_OUTPUT_REQUESTRESET_FUNC) (FMOD_OUTPUT_STATE *output_state);
58 | typedef void * (F_CALL *FMOD_OUTPUT_ALLOC_FUNC) (unsigned int size, unsigned int align, const char *file, int line);
59 | typedef void (F_CALL *FMOD_OUTPUT_FREE_FUNC) (void *ptr, const char *file, int line);
60 | typedef void (F_CALL *FMOD_OUTPUT_LOG_FUNC) (FMOD_DEBUG_FLAGS level, const char *file, int line, const char *function, const char *string, ...);
61 |
62 | /*
63 | Output structures
64 | */
65 | typedef struct FMOD_OUTPUT_DESCRIPTION
66 | {
67 | unsigned int apiversion;
68 | const char *name;
69 | unsigned int version;
70 | FMOD_OUTPUT_METHOD method;
71 | FMOD_OUTPUT_GETNUMDRIVERS_CALLBACK getnumdrivers;
72 | FMOD_OUTPUT_GETDRIVERINFO_CALLBACK getdriverinfo;
73 | FMOD_OUTPUT_INIT_CALLBACK init;
74 | FMOD_OUTPUT_START_CALLBACK start;
75 | FMOD_OUTPUT_STOP_CALLBACK stop;
76 | FMOD_OUTPUT_CLOSE_CALLBACK close;
77 | FMOD_OUTPUT_UPDATE_CALLBACK update;
78 | FMOD_OUTPUT_GETHANDLE_CALLBACK gethandle;
79 | FMOD_OUTPUT_GETPOSITION_CALLBACK getposition;
80 | FMOD_OUTPUT_LOCK_CALLBACK lock;
81 | FMOD_OUTPUT_UNLOCK_CALLBACK unlock;
82 | FMOD_OUTPUT_MIXER_CALLBACK mixer;
83 | FMOD_OUTPUT_OBJECT3DGETINFO_CALLBACK object3dgetinfo;
84 | FMOD_OUTPUT_OBJECT3DALLOC_CALLBACK object3dalloc;
85 | FMOD_OUTPUT_OBJECT3DFREE_CALLBACK object3dfree;
86 | FMOD_OUTPUT_OBJECT3DUPDATE_CALLBACK object3dupdate;
87 | FMOD_OUTPUT_OPENPORT_CALLBACK openport;
88 | FMOD_OUTPUT_CLOSEPORT_CALLBACK closeport;
89 | FMOD_OUTPUT_DEVICELISTCHANGED_CALLBACK devicelistchanged;
90 | } FMOD_OUTPUT_DESCRIPTION;
91 |
92 | struct FMOD_OUTPUT_STATE
93 | {
94 | void *plugindata;
95 | FMOD_OUTPUT_READFROMMIXER_FUNC readfrommixer;
96 | FMOD_OUTPUT_ALLOC_FUNC alloc;
97 | FMOD_OUTPUT_FREE_FUNC free;
98 | FMOD_OUTPUT_LOG_FUNC log;
99 | FMOD_OUTPUT_COPYPORT_FUNC copyport;
100 | FMOD_OUTPUT_REQUESTRESET_FUNC requestreset;
101 | };
102 |
103 | struct FMOD_OUTPUT_OBJECT3DINFO
104 | {
105 | float *buffer;
106 | unsigned int bufferlength;
107 | FMOD_VECTOR position;
108 | float gain;
109 | float spread;
110 | float priority;
111 | };
112 |
113 | /*
114 | Output macros
115 | */
116 | #define FMOD_OUTPUT_READFROMMIXER(_state, _buffer, _length) \
117 | (_state)->readfrommixer(_state, _buffer, _length)
118 | #define FMOD_OUTPUT_ALLOC(_state, _size, _align) \
119 | (_state)->alloc(_size, _align, __FILE__, __LINE__)
120 | #define FMOD_OUTPUT_FREE(_state, _ptr) \
121 | (_state)->free(_ptr, __FILE__, __LINE__)
122 | #define FMOD_OUTPUT_LOG(_state, _level, _location, _format, ...) \
123 | (_state)->log(_level, __FILE__, __LINE__, _location, _format, __VA_ARGS__)
124 | #define FMOD_OUTPUT_COPYPORT(_state, _id, _buffer, _length) \
125 | (_state)->copyport(_state, _id, _buffer, _length)
126 | #define FMOD_OUTPUT_REQUESTRESET(_state) \
127 | (_state)->requestreset(_state)
128 |
129 | #endif /* _FMOD_OUTPUT_H */
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demon/changevoice/record/PlayDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.demon.changevoice.record
2 |
3 | import android.graphics.ColorFilter
4 | import android.graphics.LightingColorFilter
5 | import android.media.MediaPlayer
6 | import android.os.Bundle
7 | import android.util.Log
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.view.WindowManager
12 | import android.widget.SeekBar
13 | import android.widget.SeekBar.OnSeekBarChangeListener
14 | import androidx.fragment.app.DialogFragment
15 | import androidx.fragment.app.FragmentManager
16 | import com.demon.changevoice.R
17 | import com.demon.changevoice.databinding.FragmentDialogPlayBinding
18 | import java.io.File
19 | import java.io.IOException
20 | import java.util.concurrent.TimeUnit
21 |
22 | /**
23 | * 播放录音的 DialogFragment
24 | *
25 | *
26 | * Created by DeMon on 2018/7/19.
27 | */
28 | class PlayDialogFragment : DialogFragment() {
29 | private var _binding: FragmentDialogPlayBinding? = null
30 | private val binding
31 | get() = _binding!!
32 | private var mMediaPlayer: MediaPlayer? = null
33 | private var isPlaying = false
34 | private var filePath: String? = null
35 |
36 | override fun onCreate(savedInstanceState: Bundle?) {
37 | super.onCreate(savedInstanceState)
38 | setStyle(STYLE_NORMAL, R.style.CustomBottomSheetStyle)
39 | filePath = arguments?.getString(FILE_PATH, "")
40 | }
41 |
42 |
43 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
44 | _binding = FragmentDialogPlayBinding.bind(inflater.inflate(R.layout.fragment_dialog_play, container, false))
45 | return _binding?.root
46 | }
47 |
48 |
49 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
50 | super.onViewCreated(view, savedInstanceState)
51 | binding.ivClose.setOnClickListener { dismiss() }
52 | val filter: ColorFilter = LightingColorFilter(resources.getColor(R.color.teal_700), resources.getColor(R.color.teal_700))
53 | binding.seekbar.progressDrawable.colorFilter = filter
54 | binding.seekbar.thumb.colorFilter = filter
55 | binding.seekbar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
56 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
57 | if (mMediaPlayer != null && fromUser) {
58 | mMediaPlayer?.seekTo(progress)
59 | binding.root.removeCallbacks(mRunnable)
60 | val minutes = TimeUnit.MILLISECONDS.toMinutes(mMediaPlayer?.currentPosition?.toLong() ?: 0)
61 | val seconds = (TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer?.currentPosition?.toLong() ?: 0)
62 | - TimeUnit.MINUTES.toSeconds(minutes))
63 | binding.currentProgressTextView.text = String.format("%02d:%02d", minutes, seconds)
64 | updateSeekBar()
65 | } else if (mMediaPlayer == null && fromUser) {
66 | prepareMediaPlayerFromPoint(progress)
67 | updateSeekBar()
68 | }
69 | }
70 |
71 | override fun onStartTrackingTouch(seekBar: SeekBar) {
72 | if (mMediaPlayer != null) {
73 | binding.root.removeCallbacks(mRunnable)
74 | }
75 | }
76 |
77 | override fun onStopTrackingTouch(seekBar: SeekBar) {
78 | if (mMediaPlayer != null) {
79 | binding.root.removeCallbacks(mRunnable)
80 | mMediaPlayer?.seekTo(seekBar.progress)
81 | val minutes = TimeUnit.MILLISECONDS.toMinutes(mMediaPlayer?.currentPosition?.toLong() ?: 0)
82 | val seconds = TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer?.currentPosition?.toLong() ?: 0) - TimeUnit.MINUTES.toSeconds(minutes)
83 | binding.currentProgressTextView.text = String.format("%02d:%02d", minutes, seconds)
84 | updateSeekBar()
85 | }
86 | }
87 | })
88 |
89 | binding.fabPlay.setOnClickListener(View.OnClickListener {
90 | onPlay(isPlaying)
91 | isPlaying = !isPlaying
92 | })
93 | val file = File(filePath)
94 | binding.fileNameTextView.text = file.name
95 | }
96 |
97 |
98 | override fun onPause() {
99 | super.onPause()
100 | stopPlaying()
101 |
102 | }
103 |
104 | override fun onDestroy() {
105 | super.onDestroy()
106 | stopPlaying()
107 | }
108 |
109 | private fun onPlay(isPlaying: Boolean) {
110 | if (!isPlaying) {
111 | if (mMediaPlayer == null) {
112 | startPlaying()
113 | } else {
114 | resumePlaying()
115 | }
116 | } else {
117 | pausePlaying()
118 | }
119 | }
120 |
121 | private fun startPlaying() {
122 | binding.fabPlay.setImageResource(R.drawable.base_record_pause)
123 | mMediaPlayer = MediaPlayer()
124 | try {
125 | mMediaPlayer?.setDataSource(filePath)
126 | mMediaPlayer?.prepare()
127 | binding.seekbar.max = mMediaPlayer?.duration ?: 0
128 | mMediaPlayer?.setOnPreparedListener {
129 | mMediaPlayer?.start()
130 | val minutes = TimeUnit.MILLISECONDS.toMinutes(mMediaPlayer?.duration?.toLong() ?: 0)
131 | val seconds = TimeUnit.MILLISECONDS.toSeconds(mMediaPlayer?.duration?.toLong() ?: 0) - TimeUnit.MINUTES.toSeconds(minutes)
132 | binding.fileLengthTextView.text = String.format("%02d:%02d", minutes, seconds)
133 | }
134 | } catch (e: IOException) {
135 | Log.e(TAG, "prepare() failed")
136 | }
137 | mMediaPlayer?.setOnCompletionListener { stopPlaying() }
138 | updateSeekBar()
139 | }
140 |
141 | private fun prepareMediaPlayerFromPoint(progress: Int) {
142 | mMediaPlayer = MediaPlayer()
143 | try {
144 | mMediaPlayer?.setDataSource(filePath)
145 | mMediaPlayer?.prepare()
146 | binding.seekbar.max = mMediaPlayer?.duration ?: 0
147 | mMediaPlayer?.seekTo(progress)
148 | mMediaPlayer?.setOnCompletionListener { stopPlaying() }
149 | } catch (e: IOException) {
150 | Log.e(TAG, "prepare() failed")
151 | }
152 |
153 | //keep screen on while playing audio
154 | activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
155 | }
156 |
157 | private fun pausePlaying() {
158 | if (mMediaPlayer != null) {
159 | binding.fabPlay.setImageResource(R.drawable.base_record_play)
160 | binding.root.removeCallbacks(mRunnable)
161 | mMediaPlayer?.pause()
162 | }
163 | }
164 |
165 | private fun resumePlaying() {
166 | if (mMediaPlayer != null) {
167 | binding.fabPlay.setImageResource(R.drawable.base_record_pause)
168 | binding.root.removeCallbacks(mRunnable)
169 | mMediaPlayer?.start()
170 | updateSeekBar()
171 | }
172 | }
173 |
174 | private fun stopPlaying() {
175 | if (mMediaPlayer != null) {
176 | binding.fabPlay.setImageResource(R.drawable.base_record_play)
177 | binding.root.removeCallbacks(mRunnable)
178 | mMediaPlayer?.stop()
179 | mMediaPlayer?.reset()
180 | mMediaPlayer?.release()
181 | mMediaPlayer = null
182 | binding.seekbar.progress = binding.seekbar.max
183 | isPlaying = !isPlaying
184 | binding.currentProgressTextView.text = binding.fileLengthTextView.text
185 | binding.seekbar.progress = binding.seekbar.max
186 | }
187 | }
188 |
189 | private val mRunnable = Runnable {
190 | if (mMediaPlayer != null) {
191 | val mCurrentPosition = mMediaPlayer?.currentPosition ?: 0
192 | binding.seekbar.progress = mCurrentPosition
193 | val minutes = TimeUnit.MILLISECONDS.toMinutes(mCurrentPosition.toLong())
194 | val seconds = (TimeUnit.MILLISECONDS.toSeconds(mCurrentPosition.toLong()) - TimeUnit.MINUTES.toSeconds(minutes))
195 | binding.currentProgressTextView.text = String.format("%02d:%02d", minutes, seconds)
196 | updateSeekBar()
197 | }
198 | }
199 |
200 | private fun updateSeekBar() {
201 | binding.root.postDelayed(mRunnable, 1000)
202 | }
203 |
204 | override fun onDestroyView() {
205 | super.onDestroyView()
206 | mMediaPlayer?.release()
207 | mMediaPlayer = null
208 | _binding = null
209 | }
210 |
211 | fun showAllowingStateLoss(manager: FragmentManager) {
212 | if (!isVisible) {
213 | manager.beginTransaction().add(this, tag).commitAllowingStateLoss()
214 | }
215 | }
216 |
217 | companion object {
218 | private const val TAG = "PlayDialogFragment"
219 | const val FILE_PATH = "FilePath"
220 | fun newInstance(filePath: String): PlayDialogFragment {
221 | val f = PlayDialogFragment()
222 | val b = Bundle()
223 | b.putString(FILE_PATH, filePath)
224 | f.arguments = b
225 | return f
226 | }
227 | }
228 | }
--------------------------------------------------------------------------------
/FmodSound/src/main/cpp/native-lib.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "inc/fmod.hpp"
3 | #include
4 | #include
5 | #include
6 |
7 | using namespace FMOD;
8 |
9 | #define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"FmodSound",FORMAT,##__VA_ARGS__);
10 | #define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"FmodSound",FORMAT,##__VA_ARGS__);
11 |
12 | #define MODE_NORMAL 0
13 | #define MODE_FUNNY 1
14 | #define MODE_UNCLE 2
15 | #define MODE_LOLITA 3
16 | #define MODE_ROBOT 4
17 | #define MODE_ETHEREAL 5
18 | #define MODE_CHORUS 6
19 | #define MODE_HORROR 7
20 |
21 | Channel *channel;
22 |
23 | extern "C"
24 | JNIEXPORT jint JNICALL
25 | Java_com_demon_fmodsound_FmodSound_saveSound(JNIEnv *env, jobject cls, jstring path_jstr, jint type, jstring save_jstr) {
26 | Sound *sound;
27 | DSP *dsp;
28 | bool playing = true;
29 | float frequency = 0;
30 | System *mSystem;
31 | JNIEnv *mEnv = env;
32 | int code = 0;
33 | System_Create(&mSystem);
34 | const char *path_cstr = mEnv->GetStringUTFChars(path_jstr, NULL);
35 | LOGI("saveAiSound-%s", path_cstr)
36 | const char *save_cstr;
37 | if (save_jstr != NULL) {
38 | save_cstr = mEnv->GetStringUTFChars(save_jstr, NULL);
39 | LOGI("saveAiSound-save_path=%s", save_cstr)
40 | }
41 | try {
42 | if (save_jstr != NULL) {
43 | char cDest[200];
44 | strcpy(cDest, save_cstr);
45 | mSystem->setSoftwareFormat(8000, FMOD_SPEAKERMODE_MONO, 0); //设置采样率为8000,channel为1
46 | mSystem->setOutput(FMOD_OUTPUTTYPE_WAVWRITER); //保存文件格式为WAV
47 | mSystem->init(32, FMOD_INIT_NORMAL, cDest);
48 | mSystem->recordStart(0, sound, true);
49 | }
50 | //创建声音
51 | mSystem->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
52 | mSystem->playSound(sound, 0, false, &channel);
53 | LOGI("saveAiSound-%s", "save_start")
54 | switch (type) {
55 | case MODE_NORMAL:
56 | LOGI("saveAiSound-%s", "save MODE_NORMAL")
57 | break;
58 | case MODE_FUNNY:
59 | LOGI("saveAiSound-%s", "save MODE_FUNNY")
60 | mSystem->createDSPByType(FMOD_DSP_TYPE_NORMALIZE, &dsp);
61 | channel->getFrequency(&frequency);
62 | frequency = frequency * 1.6;
63 | channel->setFrequency(frequency);
64 | break;
65 | case MODE_UNCLE:
66 | LOGI("saveAiSound-%s", "save MODE_UNCLE")
67 | mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
68 | dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.8);
69 | channel->addDSP(0, dsp);
70 | break;
71 | case MODE_LOLITA:
72 | LOGI("saveAiSound-%s", "save MODE_LOLITA")
73 | mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
74 | dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 1.8);
75 | channel->addDSP(0, dsp);
76 | break;
77 | case MODE_ROBOT:
78 | LOGI("saveAiSound-%s", "save MODE_ROBOT")
79 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
80 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 50);
81 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 60);
82 | channel->addDSP(0, dsp);
83 | break;
84 | case MODE_ETHEREAL:
85 | LOGI("saveAiSound-%s", "save MODE_ETHEREAL")
86 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
87 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
88 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
89 | channel->addDSP(0, dsp);
90 | break;
91 | case MODE_CHORUS:
92 | LOGI("saveAiSound-%s", "save MODE_CHORUS")
93 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
94 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 100);
95 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 50);
96 | channel->addDSP(0, dsp);
97 | break;
98 | case MODE_HORROR:
99 | LOGI("saveAiSound-%s", "save MODE_HORROR")
100 | mSystem->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
101 | dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8);
102 | channel->addDSP(0, dsp);
103 | break;
104 | default:
105 | break;
106 | }
107 | mSystem->update();
108 | } catch (...) {
109 | LOGE("saveAiSound-%s", "save error!")
110 | code = 1;
111 | goto end;
112 | }
113 | while (playing) {
114 | usleep(1000);
115 | channel->isPlaying(&playing);
116 | }
117 | LOGI("saveAiSound-%s", "save over!")
118 | goto end;
119 | end:
120 | if (path_jstr != NULL) {
121 | mEnv->ReleaseStringUTFChars(path_jstr, path_cstr);
122 | }
123 | if (save_jstr != NULL) {
124 | mEnv->ReleaseStringUTFChars(save_jstr, save_cstr);
125 | }
126 | sound->release();
127 | mSystem->close();
128 | mSystem->release();
129 | return code;
130 | }
131 | extern "C"
132 | JNIEXPORT jint JNICALL
133 | Java_com_demon_fmodsound_FmodSound_playSound(JNIEnv *env, jobject cls, jstring path_jstr, jint type) {
134 | Sound *sound;
135 | DSP *dsp;
136 | bool playing = true;
137 | float frequency = 0;
138 | System *mSystem;
139 | JNIEnv *mEnv = env;
140 | int code = 0;
141 | System_Create(&mSystem);
142 | const char *path_cstr = mEnv->GetStringUTFChars(path_jstr, NULL);
143 | LOGI("playAiSound-%s", path_cstr)
144 | try {
145 | mSystem->init(32, FMOD_INIT_NORMAL, NULL);
146 | //创建声音
147 | mSystem->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
148 | mSystem->playSound(sound, 0, false, &channel);
149 | LOGI("playAiSound-%s", "play_start")
150 | switch (type) {
151 | case MODE_NORMAL:
152 | LOGI("playAiSound-%s", "play MODE_NORMAL")
153 | break;
154 | case MODE_FUNNY:
155 | LOGI("playAiSound-%s", "play MODE_FUNNY")
156 | mSystem->createDSPByType(FMOD_DSP_TYPE_NORMALIZE, &dsp);
157 | channel->getFrequency(&frequency);
158 | frequency = frequency * 1.6;
159 | channel->setFrequency(frequency);
160 | break;
161 | case MODE_UNCLE:
162 | LOGI("playAiSound-%s", "play MODE_UNCLE")
163 | mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
164 | dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 0.8);
165 | channel->addDSP(0, dsp);
166 | break;
167 | case MODE_LOLITA:
168 | LOGI("playAiSound-%s", "play MODE_LOLITA")
169 | mSystem->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);
170 | dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 1.8);
171 | channel->addDSP(0, dsp);
172 | break;
173 | case MODE_ROBOT:
174 | LOGI("playAiSound-%s", "play MODE_ROBOT")
175 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
176 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 50);
177 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 60);
178 | channel->addDSP(0, dsp);
179 | break;
180 | case MODE_ETHEREAL:
181 | LOGI("playAiSound-%s", "play MODE_ETHEREAL")
182 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
183 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 300);
184 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 20);
185 | channel->addDSP(0, dsp);
186 | break;
187 | case MODE_CHORUS:
188 | LOGI("playAiSound-%s", "play MODE_CHORUS")
189 | mSystem->createDSPByType(FMOD_DSP_TYPE_ECHO, &dsp);
190 | dsp->setParameterFloat(FMOD_DSP_ECHO_DELAY, 100);
191 | dsp->setParameterFloat(FMOD_DSP_ECHO_FEEDBACK, 50);
192 | channel->addDSP(0, dsp);
193 | break;
194 | case MODE_HORROR:
195 | LOGI("playAiSound-%s", "play MODE_HORROR")
196 | mSystem->createDSPByType(FMOD_DSP_TYPE_TREMOLO, &dsp);
197 | dsp->setParameterFloat(FMOD_DSP_TREMOLO_SKEW, 0.8);
198 | channel->addDSP(0, dsp);
199 | break;
200 | default:
201 | break;
202 | }
203 | mSystem->update();
204 | } catch (...) {
205 | LOGE("playAiSound-%s", "play error!")
206 | code = 1;
207 | goto end;
208 | }
209 | while (playing) {
210 | usleep(1000);
211 | channel->isPlaying(&playing);
212 | }
213 | LOGI("playAiSound-%s", "play over!")
214 | goto end;
215 | end:
216 | if (path_jstr != NULL) {
217 | mEnv->ReleaseStringUTFChars(path_jstr, path_cstr);
218 | }
219 | sound->release();
220 | mSystem->close();
221 | mSystem->release();
222 | return code;
223 | }
224 |
225 |
226 | extern "C" JNIEXPORT void JNICALL
227 | Java_com_demon_fmodsound_FmodSound_stopPlay(JNIEnv *env, jobject jcls) {
228 | LOGI("%s", "stopPlay")
229 | channel->stop();
230 | }
231 |
232 | extern "C" JNIEXPORT void JNICALL
233 | Java_com_demon_fmodsound_FmodSound_resumePlay(JNIEnv *env, jobject jcls) {
234 | LOGI("%s", "resumePlay")
235 | channel->setPaused(false);
236 |
237 | }
238 |
239 | extern "C" JNIEXPORT void JNICALL
240 | Java_com_demon_fmodsound_FmodSound_pausePlay(JNIEnv *env, jobject jcls) {
241 | LOGI("%s", "pausePlay")
242 | channel->setPaused(true);
243 | }
244 |
245 | extern "C" JNIEXPORT jboolean JNICALL
246 | Java_com_demon_fmodsound_FmodSound_isPlaying(JNIEnv *env, jobject jcls) {
247 | LOGI("%s", "isPlaying")
248 | bool isPlaying = true;
249 | return !channel->isPlaying(&isPlaying);
250 |
251 | }
252 |
253 |
254 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2022] [DeMon]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/FmodSound/src/main/cpp/inc/fmod_errors.h:
--------------------------------------------------------------------------------
1 | /* ============================================================================================== */
2 | /* FMOD Core / Studio API - Error string header file. */
3 | /* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2020. */
4 | /* */
5 | /* Use this header if you want to store or display a string version / english explanation */
6 | /* of the FMOD error codes. */
7 | /* */
8 | /* For more detail visit: */
9 | /* https://fmod.com/resources/documentation-api?version=2.0&page=core-api-common.html#fmod_result */
10 | /* =============================================================================================== */
11 | #ifndef _FMOD_ERRORS_H
12 | #define _FMOD_ERRORS_H
13 |
14 | #include "fmod.h"
15 |
16 | #ifdef __GNUC__
17 | static const char *FMOD_ErrorString(FMOD_RESULT errcode) __attribute__((unused));
18 | #endif
19 |
20 | static const char *FMOD_ErrorString(FMOD_RESULT errcode)
21 | {
22 | switch (errcode)
23 | {
24 | case FMOD_OK: return "No errors.";
25 | case FMOD_ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound).";
26 | case FMOD_ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel.";
27 | case FMOD_ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound.";
28 | case FMOD_ERR_DMA: return "DMA Failure. See debug output for more information.";
29 | case FMOD_ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts.";
30 | case FMOD_ERR_DSP_DONTPROCESS: return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph.";
31 | case FMOD_ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map.";
32 | case FMOD_ERR_DSP_INUSE: return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released.";
33 | case FMOD_ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified.";
34 | case FMOD_ERR_DSP_RESERVED: return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system.";
35 | case FMOD_ERR_DSP_SILENCE: return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph.";
36 | case FMOD_ERR_DSP_TYPE: return "DSP operation cannot be performed on a DSP of this type.";
37 | case FMOD_ERR_FILE_BAD: return "Error loading file.";
38 | case FMOD_ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format.";
39 | case FMOD_ERR_FILE_DISKEJECTED: return "Media was ejected while reading.";
40 | case FMOD_ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated?).";
41 | case FMOD_ERR_FILE_ENDOFDATA: return "End of current chunk reached while trying to read data.";
42 | case FMOD_ERR_FILE_NOTFOUND: return "File not found.";
43 | case FMOD_ERR_FORMAT: return "Unsupported file or audio format.";
44 | case FMOD_ERR_HEADER_MISMATCH: return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library.";
45 | case FMOD_ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere.";
46 | case FMOD_ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden.";
47 | case FMOD_ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource.";
48 | case FMOD_ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred.";
49 | case FMOD_ERR_HTTP_TIMEOUT: return "The HTTP request timed out.";
50 | case FMOD_ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function.";
51 | case FMOD_ERR_INITIALIZED: return "Cannot call this command after System::init.";
52 | case FMOD_ERR_INTERNAL: return "An error occurred that wasn't supposed to. Contact support.";
53 | case FMOD_ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float.";
54 | case FMOD_ERR_INVALID_HANDLE: return "An invalid object handle was used.";
55 | case FMOD_ERR_INVALID_PARAM: return "An invalid parameter was passed to this function.";
56 | case FMOD_ERR_INVALID_POSITION: return "An invalid seek position was passed to this function.";
57 | case FMOD_ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode.";
58 | case FMOD_ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle.";
59 | case FMOD_ERR_INVALID_THREAD: return "Tried to call a function on a thread that is not supported.";
60 | case FMOD_ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular.";
61 | case FMOD_ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup.";
62 | case FMOD_ERR_MEMORY: return "Not enough memory or resources.";
63 | case FMOD_ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used.";
64 | case FMOD_ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound.";
65 | case FMOD_ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support.";
66 | case FMOD_ERR_NET_CONNECT: return "Couldn't connect to the specified host.";
67 | case FMOD_ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere.";
68 | case FMOD_ERR_NET_URL: return "The specified URL couldn't be resolved.";
69 | case FMOD_ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately.";
70 | case FMOD_ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready.";
71 | case FMOD_ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused.";
72 | case FMOD_ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer.";
73 | case FMOD_ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted.";
74 | case FMOD_ERR_OUTPUT_FORMAT: return "Soundcard does not support the specified format.";
75 | case FMOD_ERR_OUTPUT_INIT: return "Error initializing output device.";
76 | case FMOD_ERR_OUTPUT_NODRIVERS: return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails.";
77 | case FMOD_ERR_PLUGIN: return "An unspecified error has been returned from a plugin.";
78 | case FMOD_ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available.";
79 | case FMOD_ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback)";
80 | case FMOD_ERR_PLUGIN_VERSION: return "A plugin was built with an unsupported SDK version.";
81 | case FMOD_ERR_RECORD: return "An error occurred trying to initialize the recording device.";
82 | case FMOD_ERR_REVERB_CHANNELGROUP: return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection.";
83 | case FMOD_ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist.";
84 | case FMOD_ERR_SUBSOUNDS: return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound.";
85 | case FMOD_ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first.";
86 | case FMOD_ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file.";
87 | case FMOD_ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags.";
88 | case FMOD_ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat.";
89 | case FMOD_ERR_TRUNCATED: return "The retrieved string is too long to fit in the supplied buffer and has been truncated.";
90 | case FMOD_ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be! contact support!";
91 | case FMOD_ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called.";
92 | case FMOD_ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified.";
93 | case FMOD_ERR_VERSION: return "The version number of this file format is not supported.";
94 | case FMOD_ERR_EVENT_ALREADY_LOADED: return "The specified bank has already been loaded.";
95 | case FMOD_ERR_EVENT_LIVEUPDATE_BUSY: return "The live update connection failed due to the game already being connected.";
96 | case FMOD_ERR_EVENT_LIVEUPDATE_MISMATCH: return "The live update connection failed due to the game data being out of sync with the tool.";
97 | case FMOD_ERR_EVENT_LIVEUPDATE_TIMEOUT: return "The live update connection timed out.";
98 | case FMOD_ERR_EVENT_NOTFOUND: return "The requested event, parameter, bus or vca could not be found.";
99 | case FMOD_ERR_STUDIO_UNINITIALIZED: return "The Studio::System object is not yet initialized.";
100 | case FMOD_ERR_STUDIO_NOT_LOADED: return "The specified resource is not loaded, so it can't be unloaded.";
101 | case FMOD_ERR_INVALID_STRING: return "An invalid string was passed to this function.";
102 | case FMOD_ERR_ALREADY_LOCKED: return "The specified resource is already locked.";
103 | case FMOD_ERR_NOT_LOCKED: return "The specified resource is not locked, so it can't be unlocked.";
104 | case FMOD_ERR_RECORD_DISCONNECTED: return "The specified recording driver has been disconnected.";
105 | case FMOD_ERR_TOOMANYSAMPLES: return "The length provided exceeds the allowable limit.";
106 | default : return "Unknown error.";
107 | };
108 | }
109 |
110 | #endif
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/demon/changevoice/widget/FNRadioGroup.java:
--------------------------------------------------------------------------------
1 | package com.demon.changevoice.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.accessibility.AccessibilityEvent;
9 | import android.view.accessibility.AccessibilityNodeInfo;
10 | import android.widget.CompoundButton;
11 | import android.widget.RadioButton;
12 | import android.widget.RadioGroup;
13 |
14 | import com.demon.changevoice.R;
15 |
16 | /**
17 | * @author DeMon
18 | * Created on 2021/5/7.
19 | * E-mail 757454343@qq.com
20 | * Desc:
21 | */
22 | public class FNRadioGroup extends ViewGroup {
23 |
24 | /** 没有ID */
25 | private final static int NO_ID = -1;
26 |
27 | /** 当前选中的子控件ID */
28 | private int mCheckedId = NO_ID;
29 |
30 | /** 子控件选择改变监听器 */
31 | private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
32 |
33 | /** 为true时,不处理子控件选择事件 */
34 | private boolean mProtectFromCheckedChange = false;
35 |
36 | /** 选择改变监听器 */
37 | private OnCheckedChangeListener mOnCheckedChangeListener;
38 |
39 | /** 子控件添加移除监听器 */
40 | private PassThroughHierarchyChangeListener mPassThroughListener;
41 |
42 | /** 子控件左边距 */
43 | private int childMarginLeft = 0;
44 |
45 | /** 子控件右边距 */
46 | private int childMarginRight = 0;
47 |
48 | /** 子控件上边距 */
49 | private int childMarginTop = 0;
50 |
51 | /** 子控件下边距 */
52 | private int childMarginBottom = 0;
53 |
54 | /** 子空间高度 */
55 | private int childHeight;
56 |
57 | /**
58 | * 默认构造方法
59 | */
60 | public FNRadioGroup(Context context) {
61 | super(context);
62 | init();
63 | }
64 |
65 | /**
66 | * XML实例构造方法
67 | */
68 | public FNRadioGroup(Context context, AttributeSet attrs) {
69 | super(context, attrs);
70 |
71 | // 获取自定义属性checkedButton
72 | TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.FNRadioGroup) ;
73 | // 读取默认选中id
74 | int value = attributes.getResourceId(R.styleable.FNRadioGroup_checkedButtonId, NO_ID);
75 | if (value != NO_ID) {
76 | // 如果为设置checkButton属性,保持默认值NO_ID
77 | mCheckedId = value;
78 | }
79 | // 读取子控件左边距
80 | childMarginLeft = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginLeft, childMarginLeft);
81 | if (childMarginLeft < 0) {
82 | childMarginLeft = 0;
83 | }
84 | // 读取子控件右边距
85 | childMarginRight = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginRight, childMarginRight);
86 | if (childMarginRight < 0) {
87 | childMarginRight = 0;
88 | }
89 | // 读取子控件上边距
90 | childMarginTop = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginTop, childMarginTop);
91 | if (childMarginTop < 0) {
92 | childMarginTop = 0;
93 | }
94 | // 读取子控件下边距
95 | childMarginBottom = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginBottom, childMarginBottom);
96 | if (childMarginBottom < 0) {
97 | childMarginBottom = 0;
98 | }
99 | attributes.recycle();
100 | // 调用二级构造
101 | init();
102 | }
103 |
104 | /**
105 | * 设置子控件边距
106 | * @param l 左边距
107 | * @param t 上边距
108 | * @param r 右边距
109 | * @param b 下边距
110 | */
111 | public void setChildMargin(int l, int t, int r, int b) {
112 | childMarginTop = t;
113 | childMarginLeft = l;
114 | childMarginRight = r;
115 | childMarginBottom = b;
116 | }
117 |
118 | /**
119 | * 选中子控件为id的组件为选中项
120 | */
121 | public void check(int id) {
122 | if (id != -1 && (id == mCheckedId)) {
123 | return;
124 | }
125 | if (mCheckedId != -1) {
126 | setCheckedStateForView(mCheckedId, false);
127 | }
128 | if (id != -1) {
129 | setCheckedStateForView(id, true);
130 | }
131 | setCheckedId(id);
132 | }
133 |
134 | /**
135 | * 获取当前选中子控件的id
136 | * @return 当前选中子控件的id
137 | */
138 | public int getCheckedRadioButtonId() {
139 | return mCheckedId;
140 | }
141 |
142 | /**
143 | * 清除当前选中项
144 | */
145 | public void clearCheck() {
146 | check(-1);
147 | }
148 |
149 | /**
150 | * 设置选中改变监听
151 | * @param listener 选中改变监听
152 | */
153 | public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
154 | mOnCheckedChangeListener = listener;
155 | }
156 |
157 | /**
158 | * 布局参数
159 | */
160 | public static class LayoutParams extends ViewGroup.LayoutParams {
161 | /**
162 | * XML构造
163 | * @param c 页面引用
164 | * @param attrs XML属性集
165 | */
166 | public LayoutParams(Context c, AttributeSet attrs) {
167 | super(c, attrs);
168 | }
169 | /**
170 | * 默认构造
171 | * @param w 宽度
172 | * @param h 高度
173 | */
174 | public LayoutParams(int w, int h) {
175 | super(w, h);
176 | }
177 | /**
178 | * 父传递构造
179 | * @param p ViewGroup.LayoutParams对象
180 | */
181 | public LayoutParams(ViewGroup.LayoutParams p) {
182 | super(p);
183 | }
184 | @Override
185 | protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
186 | if (a.hasValue(widthAttr)) {
187 | width = a.getLayoutDimension(widthAttr, "layout_width");
188 | } else {
189 | width = WRAP_CONTENT;
190 | }
191 | if (a.hasValue(heightAttr)) {
192 | height = a.getLayoutDimension(heightAttr, "layout_height");
193 | } else {
194 | height = WRAP_CONTENT;
195 | }
196 | }
197 | }
198 |
199 | /**
200 | * 项目选中改变监听器
201 | */
202 | public interface OnCheckedChangeListener {
203 | /**
204 | * 选中项目改变回调
205 | * @param group 组引用
206 | * @param checkedId 改变的ID
207 | */
208 | void onCheckedChanged(FNRadioGroup group, int checkedId);
209 | }
210 |
211 | /********************************************私有方法*******************************************/
212 |
213 | /**
214 | * 二级构造方法
215 | */
216 | private void init() {
217 |
218 | // 初始化子控件选择监听
219 | mChildOnCheckedChangeListener = new CheckedStateTracker();
220 |
221 | // 初始化子控件添加移除监听器
222 | mPassThroughListener = new PassThroughHierarchyChangeListener();
223 | // 设置子控件添加移除监听器
224 | super.setOnHierarchyChangeListener(mPassThroughListener);
225 | }
226 | @Override
227 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
228 | ViewGroup.LayoutParams params = getLayoutParams();
229 | int pl = getPaddingLeft();
230 | int pr = getPaddingRight();
231 | int pt = getPaddingTop();
232 | int pb = getPaddingBottom();
233 | // 获取视图宽度
234 | int width = MeasureSpec.getSize(widthMeasureSpec);
235 | measureChildren(widthMeasureSpec, heightMeasureSpec);
236 | // 计算Tag最大高度(以此作为所有tag的高度)
237 | childHeight = 0;
238 | for (int i = 0; i < getChildCount(); i++) {
239 | int cmh = getChildAt(i).getMeasuredHeight();
240 | if (cmh > childHeight) {
241 | childHeight = cmh;
242 | }
243 | }
244 | // 计算本视图
245 | if (params.height != LayoutParams.WRAP_CONTENT) {
246 | // 非内容匹配的情况下
247 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
248 | } else {
249 | // 计算视图高度
250 | int currentHeight = pt;
251 | int currentWidth = pl;
252 | for (int i = 0; i < getChildCount(); i++) {
253 | View child = getChildAt(i);
254 | int childWidth = child.getMeasuredWidth();
255 | // 本视图加入行中是否会超过视图宽度
256 | if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) {
257 | // 累加行高读
258 | currentHeight += childMarginTop + childMarginBottom + childHeight;
259 | currentWidth = pl;
260 | currentWidth += childMarginLeft + childMarginRight + childWidth;
261 | } else {
262 | // 累加行宽度
263 | currentWidth += childMarginLeft + childMarginRight + childWidth;
264 | }
265 | }
266 | currentHeight += childMarginTop + childMarginBottom + childHeight + pb;
267 | super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY));
268 | }
269 | }
270 | @Override
271 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
272 | int pl = getPaddingLeft();
273 | int pr = getPaddingRight();
274 | int pt = getPaddingTop();
275 | int pb = getPaddingBottom();
276 | int width = r - l;
277 | // 布局Tag视图
278 | int currentHeight = pt;
279 | int currentWidth = pl;
280 | for (int i=0; i < getChildCount(); i++) {
281 | View child = getChildAt(i);
282 | int childWidth = child.getMeasuredWidth();
283 | // 本视图加入行中是否会超过视图宽度
284 | if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) {
285 | // 累加行高读
286 | currentHeight += childMarginTop + childMarginBottom + childHeight;
287 | currentWidth = pl;
288 | // 布局视图
289 | child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop,
290 | currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight);
291 | currentWidth += childMarginLeft + childMarginRight + childWidth;
292 | } else {
293 | // 布局视图
294 | child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop,
295 | currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight);
296 | // 累加行宽度
297 | currentWidth += childMarginLeft + childMarginRight + childWidth;
298 | }
299 | }
300 | }
301 | @Override
302 | public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
303 | // 设置子空间添加移除监听
304 | mPassThroughListener.mOnHierarchyChangeListener = listener;
305 | }
306 | @Override
307 | protected void onFinishInflate() {
308 | super.onFinishInflate();
309 | if (mCheckedId != NO_ID) {
310 | // 如果读取到选中项,设置并存储选中项
311 | mProtectFromCheckedChange = true;
312 | setCheckedStateForView(mCheckedId, true);
313 | mProtectFromCheckedChange = false;
314 | setCheckedId(mCheckedId);
315 | }
316 | }
317 | @Override
318 | public void addView(View child, int index, ViewGroup.LayoutParams params) {
319 | if (child instanceof RadioButton) {
320 | final RadioButton button = (RadioButton) child;
321 | if (button.isChecked()) {
322 | mProtectFromCheckedChange = true;
323 | if (mCheckedId != -1) {
324 | setCheckedStateForView(mCheckedId, false);
325 | }
326 | mProtectFromCheckedChange = false;
327 | setCheckedId(button.getId());
328 | }
329 | }
330 |
331 | super.addView(child, index, params);
332 | }
333 | private void setCheckedId(int id) {
334 | mCheckedId = id;
335 | if (mOnCheckedChangeListener != null) {
336 | mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
337 | }
338 | }
339 | private void setCheckedStateForView(int viewId, boolean checked) {
340 | View checkedView = findViewById(viewId);
341 | if (checkedView != null && checkedView instanceof RadioButton) {
342 | ((RadioButton) checkedView).setChecked(checked);
343 | }
344 | }
345 | @Override
346 | public LayoutParams generateLayoutParams(AttributeSet attrs) {
347 | return new FNRadioGroup.LayoutParams(getContext(), attrs);
348 | }
349 | @Override
350 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
351 | return p instanceof RadioGroup.LayoutParams;
352 | }
353 | @Override
354 | protected LayoutParams generateDefaultLayoutParams() {
355 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
356 | }
357 | @Override
358 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
359 | super.onInitializeAccessibilityEvent(event);
360 | event.setClassName(RadioGroup.class.getName());
361 | }
362 | @Override
363 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
364 | super.onInitializeAccessibilityNodeInfo(info);
365 | info.setClassName(RadioGroup.class.getName());
366 | }
367 | private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
368 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
369 | // prevents from infinite recursion
370 | if (mProtectFromCheckedChange) {
371 | return;
372 | }
373 | mProtectFromCheckedChange = true;
374 | if (mCheckedId != -1) {
375 | setCheckedStateForView(mCheckedId, false);
376 | }
377 | mProtectFromCheckedChange = false;
378 | int id = buttonView.getId();
379 | setCheckedId(id);
380 | }
381 | }
382 | private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener {
383 | private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
384 | public void onChildViewAdded(View parent, View child) {
385 | if (parent == FNRadioGroup.this && child instanceof RadioButton) {
386 | int id = child.getId();
387 | // generates an id if it's missing
388 | if (id == View.NO_ID) {
389 | id = generateViewId();
390 | child.setId(id);
391 | }
392 | ((RadioButton) child).setOnCheckedChangeListener(mChildOnCheckedChangeListener);
393 | }
394 |
395 | if (mOnHierarchyChangeListener != null) {
396 | mOnHierarchyChangeListener.onChildViewAdded(parent, child);
397 | }
398 | }
399 | public void onChildViewRemoved(View parent, View child) {
400 | if (parent == FNRadioGroup.this && child instanceof RadioButton) {
401 | ((RadioButton) child).setOnCheckedChangeListener(null);
402 | }
403 | if (mOnHierarchyChangeListener != null) {
404 | mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
405 | }
406 | }
407 | }
408 | }
409 |
--------------------------------------------------------------------------------
/FmodSound/src/main/cpp/inc/fmod_dsp_effects.h:
--------------------------------------------------------------------------------
1 | /* ============================================================================================================= */
2 | /* FMOD Core API - Built-in effects header file. */
3 | /* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2020. */
4 | /* */
5 | /* In this header you can find parameter structures for FMOD system registered DSP effects */
6 | /* and generators. */
7 | /* */
8 | /* For more detail visit: */
9 | /* https://fmod.com/resources/documentation-api?version=2.0&page=core-api-common-dsp-effects.html#fmod_dsp_type */
10 | /* ============================================================================================================= */
11 |
12 | #ifndef _FMOD_DSP_EFFECTS_H
13 | #define _FMOD_DSP_EFFECTS_H
14 |
15 | typedef enum
16 | {
17 | FMOD_DSP_TYPE_UNKNOWN,
18 | FMOD_DSP_TYPE_MIXER,
19 | FMOD_DSP_TYPE_OSCILLATOR,
20 | FMOD_DSP_TYPE_LOWPASS,
21 | FMOD_DSP_TYPE_ITLOWPASS,
22 | FMOD_DSP_TYPE_HIGHPASS,
23 | FMOD_DSP_TYPE_ECHO,
24 | FMOD_DSP_TYPE_FADER,
25 | FMOD_DSP_TYPE_FLANGE,
26 | FMOD_DSP_TYPE_DISTORTION,
27 | FMOD_DSP_TYPE_NORMALIZE,
28 | FMOD_DSP_TYPE_LIMITER,
29 | FMOD_DSP_TYPE_PARAMEQ,
30 | FMOD_DSP_TYPE_PITCHSHIFT,
31 | FMOD_DSP_TYPE_CHORUS,
32 | FMOD_DSP_TYPE_VSTPLUGIN,
33 | FMOD_DSP_TYPE_WINAMPPLUGIN,
34 | FMOD_DSP_TYPE_ITECHO,
35 | FMOD_DSP_TYPE_COMPRESSOR,
36 | FMOD_DSP_TYPE_SFXREVERB,
37 | FMOD_DSP_TYPE_LOWPASS_SIMPLE,
38 | FMOD_DSP_TYPE_DELAY,
39 | FMOD_DSP_TYPE_TREMOLO,
40 | FMOD_DSP_TYPE_LADSPAPLUGIN,
41 | FMOD_DSP_TYPE_SEND,
42 | FMOD_DSP_TYPE_RETURN,
43 | FMOD_DSP_TYPE_HIGHPASS_SIMPLE,
44 | FMOD_DSP_TYPE_PAN,
45 | FMOD_DSP_TYPE_THREE_EQ,
46 | FMOD_DSP_TYPE_FFT,
47 | FMOD_DSP_TYPE_LOUDNESS_METER,
48 | FMOD_DSP_TYPE_ENVELOPEFOLLOWER,
49 | FMOD_DSP_TYPE_CONVOLUTIONREVERB,
50 | FMOD_DSP_TYPE_CHANNELMIX,
51 | FMOD_DSP_TYPE_TRANSCEIVER,
52 | FMOD_DSP_TYPE_OBJECTPAN,
53 | FMOD_DSP_TYPE_MULTIBAND_EQ,
54 |
55 | FMOD_DSP_TYPE_MAX,
56 | FMOD_DSP_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */
57 | } FMOD_DSP_TYPE;
58 |
59 | /*
60 | ===================================================================================================
61 |
62 | FMOD built in effect parameters.
63 | Use DSP::setParameter with these enums for the 'index' parameter.
64 |
65 | ===================================================================================================
66 | */
67 |
68 | typedef enum
69 | {
70 | FMOD_DSP_OSCILLATOR_TYPE,
71 | FMOD_DSP_OSCILLATOR_RATE
72 | } FMOD_DSP_OSCILLATOR;
73 |
74 |
75 | typedef enum
76 | {
77 | FMOD_DSP_LOWPASS_CUTOFF,
78 | FMOD_DSP_LOWPASS_RESONANCE
79 | } FMOD_DSP_LOWPASS;
80 |
81 |
82 | typedef enum
83 | {
84 | FMOD_DSP_ITLOWPASS_CUTOFF,
85 | FMOD_DSP_ITLOWPASS_RESONANCE
86 | } FMOD_DSP_ITLOWPASS;
87 |
88 |
89 | typedef enum
90 | {
91 | FMOD_DSP_HIGHPASS_CUTOFF,
92 | FMOD_DSP_HIGHPASS_RESONANCE
93 | } FMOD_DSP_HIGHPASS;
94 |
95 |
96 | typedef enum
97 | {
98 | FMOD_DSP_ECHO_DELAY,
99 | FMOD_DSP_ECHO_FEEDBACK,
100 | FMOD_DSP_ECHO_DRYLEVEL,
101 | FMOD_DSP_ECHO_WETLEVEL
102 | } FMOD_DSP_ECHO;
103 |
104 |
105 | typedef enum FMOD_DSP_FADER
106 | {
107 | FMOD_DSP_FADER_GAIN,
108 | FMOD_DSP_FADER_OVERALL_GAIN,
109 | } FMOD_DSP_FADER;
110 |
111 |
112 | typedef enum
113 | {
114 | FMOD_DSP_FLANGE_MIX,
115 | FMOD_DSP_FLANGE_DEPTH,
116 | FMOD_DSP_FLANGE_RATE
117 | } FMOD_DSP_FLANGE;
118 |
119 |
120 | typedef enum
121 | {
122 | FMOD_DSP_DISTORTION_LEVEL
123 | } FMOD_DSP_DISTORTION;
124 |
125 |
126 | typedef enum
127 | {
128 | FMOD_DSP_NORMALIZE_FADETIME,
129 | FMOD_DSP_NORMALIZE_THRESHHOLD,
130 | FMOD_DSP_NORMALIZE_MAXAMP
131 | } FMOD_DSP_NORMALIZE;
132 |
133 |
134 | typedef enum
135 | {
136 | FMOD_DSP_LIMITER_RELEASETIME,
137 | FMOD_DSP_LIMITER_CEILING,
138 | FMOD_DSP_LIMITER_MAXIMIZERGAIN,
139 | FMOD_DSP_LIMITER_MODE,
140 | } FMOD_DSP_LIMITER;
141 |
142 |
143 | typedef enum
144 | {
145 | FMOD_DSP_PARAMEQ_CENTER,
146 | FMOD_DSP_PARAMEQ_BANDWIDTH,
147 | FMOD_DSP_PARAMEQ_GAIN
148 | } FMOD_DSP_PARAMEQ;
149 |
150 |
151 | typedef enum FMOD_DSP_MULTIBAND_EQ
152 | {
153 | FMOD_DSP_MULTIBAND_EQ_A_FILTER,
154 | FMOD_DSP_MULTIBAND_EQ_A_FREQUENCY,
155 | FMOD_DSP_MULTIBAND_EQ_A_Q,
156 | FMOD_DSP_MULTIBAND_EQ_A_GAIN,
157 | FMOD_DSP_MULTIBAND_EQ_B_FILTER,
158 | FMOD_DSP_MULTIBAND_EQ_B_FREQUENCY,
159 | FMOD_DSP_MULTIBAND_EQ_B_Q,
160 | FMOD_DSP_MULTIBAND_EQ_B_GAIN,
161 | FMOD_DSP_MULTIBAND_EQ_C_FILTER,
162 | FMOD_DSP_MULTIBAND_EQ_C_FREQUENCY,
163 | FMOD_DSP_MULTIBAND_EQ_C_Q,
164 | FMOD_DSP_MULTIBAND_EQ_C_GAIN,
165 | FMOD_DSP_MULTIBAND_EQ_D_FILTER,
166 | FMOD_DSP_MULTIBAND_EQ_D_FREQUENCY,
167 | FMOD_DSP_MULTIBAND_EQ_D_Q,
168 | FMOD_DSP_MULTIBAND_EQ_D_GAIN,
169 | FMOD_DSP_MULTIBAND_EQ_E_FILTER,
170 | FMOD_DSP_MULTIBAND_EQ_E_FREQUENCY,
171 | FMOD_DSP_MULTIBAND_EQ_E_Q,
172 | FMOD_DSP_MULTIBAND_EQ_E_GAIN,
173 | } FMOD_DSP_MULTIBAND_EQ;
174 |
175 |
176 | typedef enum FMOD_DSP_MULTIBAND_EQ_FILTER_TYPE
177 | {
178 | FMOD_DSP_MULTIBAND_EQ_FILTER_DISABLED,
179 | FMOD_DSP_MULTIBAND_EQ_FILTER_LOWPASS_12DB,
180 | FMOD_DSP_MULTIBAND_EQ_FILTER_LOWPASS_24DB,
181 | FMOD_DSP_MULTIBAND_EQ_FILTER_LOWPASS_48DB,
182 | FMOD_DSP_MULTIBAND_EQ_FILTER_HIGHPASS_12DB,
183 | FMOD_DSP_MULTIBAND_EQ_FILTER_HIGHPASS_24DB,
184 | FMOD_DSP_MULTIBAND_EQ_FILTER_HIGHPASS_48DB,
185 | FMOD_DSP_MULTIBAND_EQ_FILTER_LOWSHELF,
186 | FMOD_DSP_MULTIBAND_EQ_FILTER_HIGHSHELF,
187 | FMOD_DSP_MULTIBAND_EQ_FILTER_PEAKING,
188 | FMOD_DSP_MULTIBAND_EQ_FILTER_BANDPASS,
189 | FMOD_DSP_MULTIBAND_EQ_FILTER_NOTCH,
190 | FMOD_DSP_MULTIBAND_EQ_FILTER_ALLPASS,
191 | } FMOD_DSP_MULTIBAND_EQ_FILTER_TYPE;
192 |
193 |
194 | typedef enum
195 | {
196 | FMOD_DSP_PITCHSHIFT_PITCH,
197 | FMOD_DSP_PITCHSHIFT_FFTSIZE,
198 | FMOD_DSP_PITCHSHIFT_OVERLAP,
199 | FMOD_DSP_PITCHSHIFT_MAXCHANNELS
200 | } FMOD_DSP_PITCHSHIFT;
201 |
202 |
203 | typedef enum
204 | {
205 | FMOD_DSP_CHORUS_MIX,
206 | FMOD_DSP_CHORUS_RATE,
207 | FMOD_DSP_CHORUS_DEPTH,
208 | } FMOD_DSP_CHORUS;
209 |
210 |
211 | typedef enum
212 | {
213 | FMOD_DSP_ITECHO_WETDRYMIX,
214 | FMOD_DSP_ITECHO_FEEDBACK,
215 | FMOD_DSP_ITECHO_LEFTDELAY,
216 | FMOD_DSP_ITECHO_RIGHTDELAY,
217 | FMOD_DSP_ITECHO_PANDELAY
218 | } FMOD_DSP_ITECHO;
219 |
220 | typedef enum
221 | {
222 | FMOD_DSP_COMPRESSOR_THRESHOLD,
223 | FMOD_DSP_COMPRESSOR_RATIO,
224 | FMOD_DSP_COMPRESSOR_ATTACK,
225 | FMOD_DSP_COMPRESSOR_RELEASE,
226 | FMOD_DSP_COMPRESSOR_GAINMAKEUP,
227 | FMOD_DSP_COMPRESSOR_USESIDECHAIN,
228 | FMOD_DSP_COMPRESSOR_LINKED
229 | } FMOD_DSP_COMPRESSOR;
230 |
231 | typedef enum
232 | {
233 | FMOD_DSP_SFXREVERB_DECAYTIME,
234 | FMOD_DSP_SFXREVERB_EARLYDELAY,
235 | FMOD_DSP_SFXREVERB_LATEDELAY,
236 | FMOD_DSP_SFXREVERB_HFREFERENCE,
237 | FMOD_DSP_SFXREVERB_HFDECAYRATIO,
238 | FMOD_DSP_SFXREVERB_DIFFUSION,
239 | FMOD_DSP_SFXREVERB_DENSITY,
240 | FMOD_DSP_SFXREVERB_LOWSHELFFREQUENCY,
241 | FMOD_DSP_SFXREVERB_LOWSHELFGAIN,
242 | FMOD_DSP_SFXREVERB_HIGHCUT,
243 | FMOD_DSP_SFXREVERB_EARLYLATEMIX,
244 | FMOD_DSP_SFXREVERB_WETLEVEL,
245 | FMOD_DSP_SFXREVERB_DRYLEVEL
246 | } FMOD_DSP_SFXREVERB;
247 |
248 | typedef enum
249 | {
250 | FMOD_DSP_LOWPASS_SIMPLE_CUTOFF
251 | } FMOD_DSP_LOWPASS_SIMPLE;
252 |
253 |
254 | typedef enum
255 | {
256 | FMOD_DSP_DELAY_CH0,
257 | FMOD_DSP_DELAY_CH1,
258 | FMOD_DSP_DELAY_CH2,
259 | FMOD_DSP_DELAY_CH3,
260 | FMOD_DSP_DELAY_CH4,
261 | FMOD_DSP_DELAY_CH5,
262 | FMOD_DSP_DELAY_CH6,
263 | FMOD_DSP_DELAY_CH7,
264 | FMOD_DSP_DELAY_CH8,
265 | FMOD_DSP_DELAY_CH9,
266 | FMOD_DSP_DELAY_CH10,
267 | FMOD_DSP_DELAY_CH11,
268 | FMOD_DSP_DELAY_CH12,
269 | FMOD_DSP_DELAY_CH13,
270 | FMOD_DSP_DELAY_CH14,
271 | FMOD_DSP_DELAY_CH15,
272 | FMOD_DSP_DELAY_MAXDELAY
273 | } FMOD_DSP_DELAY;
274 |
275 |
276 | typedef enum
277 | {
278 | FMOD_DSP_TREMOLO_FREQUENCY,
279 | FMOD_DSP_TREMOLO_DEPTH,
280 | FMOD_DSP_TREMOLO_SHAPE,
281 | FMOD_DSP_TREMOLO_SKEW,
282 | FMOD_DSP_TREMOLO_DUTY,
283 | FMOD_DSP_TREMOLO_SQUARE,
284 | FMOD_DSP_TREMOLO_PHASE,
285 | FMOD_DSP_TREMOLO_SPREAD
286 | } FMOD_DSP_TREMOLO;
287 |
288 |
289 | typedef enum
290 | {
291 | FMOD_DSP_SEND_RETURNID,
292 | FMOD_DSP_SEND_LEVEL,
293 | } FMOD_DSP_SEND;
294 |
295 |
296 | typedef enum
297 | {
298 | FMOD_DSP_RETURN_ID,
299 | FMOD_DSP_RETURN_INPUT_SPEAKER_MODE
300 | } FMOD_DSP_RETURN;
301 |
302 |
303 | typedef enum
304 | {
305 | FMOD_DSP_HIGHPASS_SIMPLE_CUTOFF
306 | } FMOD_DSP_HIGHPASS_SIMPLE;
307 |
308 |
309 | typedef enum
310 | {
311 | FMOD_DSP_PAN_2D_STEREO_MODE_DISTRIBUTED,
312 | FMOD_DSP_PAN_2D_STEREO_MODE_DISCRETE
313 | } FMOD_DSP_PAN_2D_STEREO_MODE_TYPE;
314 |
315 |
316 | typedef enum
317 | {
318 | FMOD_DSP_PAN_MODE_MONO,
319 | FMOD_DSP_PAN_MODE_STEREO,
320 | FMOD_DSP_PAN_MODE_SURROUND
321 | } FMOD_DSP_PAN_MODE_TYPE;
322 |
323 |
324 | typedef enum
325 | {
326 | FMOD_DSP_PAN_3D_ROLLOFF_LINEARSQUARED,
327 | FMOD_DSP_PAN_3D_ROLLOFF_LINEAR,
328 | FMOD_DSP_PAN_3D_ROLLOFF_INVERSE,
329 | FMOD_DSP_PAN_3D_ROLLOFF_INVERSETAPERED,
330 | FMOD_DSP_PAN_3D_ROLLOFF_CUSTOM
331 | } FMOD_DSP_PAN_3D_ROLLOFF_TYPE;
332 |
333 |
334 | typedef enum
335 | {
336 | FMOD_DSP_PAN_3D_EXTENT_MODE_AUTO,
337 | FMOD_DSP_PAN_3D_EXTENT_MODE_USER,
338 | FMOD_DSP_PAN_3D_EXTENT_MODE_OFF
339 | } FMOD_DSP_PAN_3D_EXTENT_MODE_TYPE;
340 |
341 |
342 | typedef enum
343 | {
344 | FMOD_DSP_PAN_MODE,
345 | FMOD_DSP_PAN_2D_STEREO_POSITION,
346 | FMOD_DSP_PAN_2D_DIRECTION,
347 | FMOD_DSP_PAN_2D_EXTENT,
348 | FMOD_DSP_PAN_2D_ROTATION,
349 | FMOD_DSP_PAN_2D_LFE_LEVEL,
350 | FMOD_DSP_PAN_2D_STEREO_MODE,
351 | FMOD_DSP_PAN_2D_STEREO_SEPARATION,
352 | FMOD_DSP_PAN_2D_STEREO_AXIS,
353 | FMOD_DSP_PAN_ENABLED_SPEAKERS,
354 | FMOD_DSP_PAN_3D_POSITION,
355 | FMOD_DSP_PAN_3D_ROLLOFF,
356 | FMOD_DSP_PAN_3D_MIN_DISTANCE,
357 | FMOD_DSP_PAN_3D_MAX_DISTANCE,
358 | FMOD_DSP_PAN_3D_EXTENT_MODE,
359 | FMOD_DSP_PAN_3D_SOUND_SIZE,
360 | FMOD_DSP_PAN_3D_MIN_EXTENT,
361 | FMOD_DSP_PAN_3D_PAN_BLEND,
362 | FMOD_DSP_PAN_LFE_UPMIX_ENABLED,
363 | FMOD_DSP_PAN_OVERALL_GAIN,
364 | FMOD_DSP_PAN_SURROUND_SPEAKER_MODE,
365 | FMOD_DSP_PAN_2D_HEIGHT_BLEND,
366 | } FMOD_DSP_PAN;
367 |
368 |
369 | typedef enum
370 | {
371 | FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_12DB,
372 | FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_24DB,
373 | FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_48DB
374 | } FMOD_DSP_THREE_EQ_CROSSOVERSLOPE_TYPE;
375 |
376 |
377 | typedef enum
378 | {
379 | FMOD_DSP_THREE_EQ_LOWGAIN,
380 | FMOD_DSP_THREE_EQ_MIDGAIN,
381 | FMOD_DSP_THREE_EQ_HIGHGAIN,
382 | FMOD_DSP_THREE_EQ_LOWCROSSOVER,
383 | FMOD_DSP_THREE_EQ_HIGHCROSSOVER,
384 | FMOD_DSP_THREE_EQ_CROSSOVERSLOPE
385 | } FMOD_DSP_THREE_EQ;
386 |
387 |
388 | typedef enum
389 | {
390 | FMOD_DSP_FFT_WINDOW_RECT,
391 | FMOD_DSP_FFT_WINDOW_TRIANGLE,
392 | FMOD_DSP_FFT_WINDOW_HAMMING,
393 | FMOD_DSP_FFT_WINDOW_HANNING,
394 | FMOD_DSP_FFT_WINDOW_BLACKMAN,
395 | FMOD_DSP_FFT_WINDOW_BLACKMANHARRIS
396 | } FMOD_DSP_FFT_WINDOW;
397 |
398 |
399 | typedef enum
400 | {
401 | FMOD_DSP_FFT_WINDOWSIZE,
402 | FMOD_DSP_FFT_WINDOWTYPE,
403 | FMOD_DSP_FFT_SPECTRUMDATA,
404 | FMOD_DSP_FFT_DOMINANT_FREQ
405 | } FMOD_DSP_FFT;
406 |
407 |
408 | typedef enum
409 | {
410 | FMOD_DSP_ENVELOPEFOLLOWER_ATTACK,
411 | FMOD_DSP_ENVELOPEFOLLOWER_RELEASE,
412 | FMOD_DSP_ENVELOPEFOLLOWER_ENVELOPE,
413 | FMOD_DSP_ENVELOPEFOLLOWER_USESIDECHAIN
414 | } FMOD_DSP_ENVELOPEFOLLOWER;
415 |
416 | typedef enum
417 | {
418 | FMOD_DSP_CONVOLUTION_REVERB_PARAM_IR,
419 | FMOD_DSP_CONVOLUTION_REVERB_PARAM_WET,
420 | FMOD_DSP_CONVOLUTION_REVERB_PARAM_DRY,
421 | FMOD_DSP_CONVOLUTION_REVERB_PARAM_LINKED
422 | } FMOD_DSP_CONVOLUTION_REVERB;
423 |
424 | typedef enum
425 | {
426 | FMOD_DSP_CHANNELMIX_OUTPUT_DEFAULT,
427 | FMOD_DSP_CHANNELMIX_OUTPUT_ALLMONO,
428 | FMOD_DSP_CHANNELMIX_OUTPUT_ALLSTEREO,
429 | FMOD_DSP_CHANNELMIX_OUTPUT_ALLQUAD,
430 | FMOD_DSP_CHANNELMIX_OUTPUT_ALL5POINT1,
431 | FMOD_DSP_CHANNELMIX_OUTPUT_ALL7POINT1,
432 | FMOD_DSP_CHANNELMIX_OUTPUT_ALLLFE,
433 | FMOD_DSP_CHANNELMIX_OUTPUT_ALL7POINT1POINT4
434 | } FMOD_DSP_CHANNELMIX_OUTPUT;
435 |
436 | typedef enum
437 | {
438 | FMOD_DSP_CHANNELMIX_OUTPUTGROUPING,
439 | FMOD_DSP_CHANNELMIX_GAIN_CH0,
440 | FMOD_DSP_CHANNELMIX_GAIN_CH1,
441 | FMOD_DSP_CHANNELMIX_GAIN_CH2,
442 | FMOD_DSP_CHANNELMIX_GAIN_CH3,
443 | FMOD_DSP_CHANNELMIX_GAIN_CH4,
444 | FMOD_DSP_CHANNELMIX_GAIN_CH5,
445 | FMOD_DSP_CHANNELMIX_GAIN_CH6,
446 | FMOD_DSP_CHANNELMIX_GAIN_CH7,
447 | FMOD_DSP_CHANNELMIX_GAIN_CH8,
448 | FMOD_DSP_CHANNELMIX_GAIN_CH9,
449 | FMOD_DSP_CHANNELMIX_GAIN_CH10,
450 | FMOD_DSP_CHANNELMIX_GAIN_CH11,
451 | FMOD_DSP_CHANNELMIX_GAIN_CH12,
452 | FMOD_DSP_CHANNELMIX_GAIN_CH13,
453 | FMOD_DSP_CHANNELMIX_GAIN_CH14,
454 | FMOD_DSP_CHANNELMIX_GAIN_CH15,
455 | FMOD_DSP_CHANNELMIX_GAIN_CH16,
456 | FMOD_DSP_CHANNELMIX_GAIN_CH17,
457 | FMOD_DSP_CHANNELMIX_GAIN_CH18,
458 | FMOD_DSP_CHANNELMIX_GAIN_CH19,
459 | FMOD_DSP_CHANNELMIX_GAIN_CH20,
460 | FMOD_DSP_CHANNELMIX_GAIN_CH21,
461 | FMOD_DSP_CHANNELMIX_GAIN_CH22,
462 | FMOD_DSP_CHANNELMIX_GAIN_CH23,
463 | FMOD_DSP_CHANNELMIX_GAIN_CH24,
464 | FMOD_DSP_CHANNELMIX_GAIN_CH25,
465 | FMOD_DSP_CHANNELMIX_GAIN_CH26,
466 | FMOD_DSP_CHANNELMIX_GAIN_CH27,
467 | FMOD_DSP_CHANNELMIX_GAIN_CH28,
468 | FMOD_DSP_CHANNELMIX_GAIN_CH29,
469 | FMOD_DSP_CHANNELMIX_GAIN_CH30,
470 | FMOD_DSP_CHANNELMIX_GAIN_CH31,
471 | FMOD_DSP_CHANNELMIX_OUTPUT_CH0,
472 | FMOD_DSP_CHANNELMIX_OUTPUT_CH1,
473 | FMOD_DSP_CHANNELMIX_OUTPUT_CH2,
474 | FMOD_DSP_CHANNELMIX_OUTPUT_CH3,
475 | FMOD_DSP_CHANNELMIX_OUTPUT_CH4,
476 | FMOD_DSP_CHANNELMIX_OUTPUT_CH5,
477 | FMOD_DSP_CHANNELMIX_OUTPUT_CH6,
478 | FMOD_DSP_CHANNELMIX_OUTPUT_CH7,
479 | FMOD_DSP_CHANNELMIX_OUTPUT_CH8,
480 | FMOD_DSP_CHANNELMIX_OUTPUT_CH9,
481 | FMOD_DSP_CHANNELMIX_OUTPUT_CH10,
482 | FMOD_DSP_CHANNELMIX_OUTPUT_CH11,
483 | FMOD_DSP_CHANNELMIX_OUTPUT_CH12,
484 | FMOD_DSP_CHANNELMIX_OUTPUT_CH13,
485 | FMOD_DSP_CHANNELMIX_OUTPUT_CH14,
486 | FMOD_DSP_CHANNELMIX_OUTPUT_CH15,
487 | FMOD_DSP_CHANNELMIX_OUTPUT_CH16,
488 | FMOD_DSP_CHANNELMIX_OUTPUT_CH17,
489 | FMOD_DSP_CHANNELMIX_OUTPUT_CH18,
490 | FMOD_DSP_CHANNELMIX_OUTPUT_CH19,
491 | FMOD_DSP_CHANNELMIX_OUTPUT_CH20,
492 | FMOD_DSP_CHANNELMIX_OUTPUT_CH21,
493 | FMOD_DSP_CHANNELMIX_OUTPUT_CH22,
494 | FMOD_DSP_CHANNELMIX_OUTPUT_CH23,
495 | FMOD_DSP_CHANNELMIX_OUTPUT_CH24,
496 | FMOD_DSP_CHANNELMIX_OUTPUT_CH25,
497 | FMOD_DSP_CHANNELMIX_OUTPUT_CH26,
498 | FMOD_DSP_CHANNELMIX_OUTPUT_CH27,
499 | FMOD_DSP_CHANNELMIX_OUTPUT_CH28,
500 | FMOD_DSP_CHANNELMIX_OUTPUT_CH29,
501 | FMOD_DSP_CHANNELMIX_OUTPUT_CH30,
502 | FMOD_DSP_CHANNELMIX_OUTPUT_CH31
503 | } FMOD_DSP_CHANNELMIX;
504 |
505 | typedef enum
506 | {
507 | FMOD_DSP_TRANSCEIVER_SPEAKERMODE_AUTO = -1,
508 | FMOD_DSP_TRANSCEIVER_SPEAKERMODE_MONO = 0,
509 | FMOD_DSP_TRANSCEIVER_SPEAKERMODE_STEREO,
510 | FMOD_DSP_TRANSCEIVER_SPEAKERMODE_SURROUND,
511 | } FMOD_DSP_TRANSCEIVER_SPEAKERMODE;
512 |
513 |
514 | typedef enum
515 | {
516 | FMOD_DSP_TRANSCEIVER_TRANSMIT,
517 | FMOD_DSP_TRANSCEIVER_GAIN,
518 | FMOD_DSP_TRANSCEIVER_CHANNEL,
519 | FMOD_DSP_TRANSCEIVER_TRANSMITSPEAKERMODE
520 | } FMOD_DSP_TRANSCEIVER;
521 |
522 |
523 | typedef enum
524 | {
525 | FMOD_DSP_OBJECTPAN_3D_POSITION,
526 | FMOD_DSP_OBJECTPAN_3D_ROLLOFF,
527 | FMOD_DSP_OBJECTPAN_3D_MIN_DISTANCE,
528 | FMOD_DSP_OBJECTPAN_3D_MAX_DISTANCE,
529 | FMOD_DSP_OBJECTPAN_3D_EXTENT_MODE,
530 | FMOD_DSP_OBJECTPAN_3D_SOUND_SIZE,
531 | FMOD_DSP_OBJECTPAN_3D_MIN_EXTENT,
532 | FMOD_DSP_OBJECTPAN_OVERALL_GAIN,
533 | FMOD_DSP_OBJECTPAN_OUTPUTGAIN
534 | } FMOD_DSP_OBJECTPAN;
535 |
536 | #endif
537 |
538 |
--------------------------------------------------------------------------------