├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── Voicelibrary ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jason │ │ └── voicelibrary │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jason │ │ │ ├── VoiceProcessor.java │ │ │ └── voicelibrary │ │ │ ├── Player.java │ │ │ ├── PlayerThread.java │ │ │ ├── RecordListener.java │ │ │ ├── RecordThread.java │ │ │ ├── Recorder.java │ │ │ ├── utils │ │ │ └── FileUtil.java │ │ │ └── view │ │ │ ├── RecordViewDialog.java │ │ │ └── VoiceLineView.java │ ├── jniLibs │ │ ├── armeabi-v7a │ │ │ └── libsmbPitchShift.so │ │ ├── armeabi │ │ │ └── libsmbPitchShift.so │ │ └── x86 │ │ │ └── libsmbPitchShift.so │ └── res │ │ ├── drawable │ │ ├── delete.png │ │ ├── hollow_circle_bg.xml │ │ ├── hollow_circle_click.xml │ │ ├── hollow_circle_p_bg.xml │ │ └── shape_circular_bg.xml │ │ ├── layout │ │ └── record_view_dialog.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── style.xml │ └── test │ └── java │ └── com │ └── jason │ └── voicelibrary │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jason │ │ └── voice │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jason │ │ │ └── voice │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── 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 │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jason │ └── voice │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /Voicelibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Voicelibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | def globalConfig = rootProject.ext.android 5 | compileSdkVersion globalConfig.compileSdkVersion as int 6 | buildToolsVersion globalConfig.buildToolsVersion as String 7 | 8 | defaultConfig { 9 | minSdkVersion globalConfig.minSdkVersion as int 10 | targetSdkVersion globalConfig.targetSdkVersion as int 11 | versionCode globalConfig.versionCode as int 12 | versionName globalConfig.versionName as String 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.3.1' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /Voicelibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Admin\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /Voicelibrary/src/androidTest/java/com/jason/voicelibrary/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jason.muelibrary.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/VoiceProcessor.java: -------------------------------------------------------------------------------- 1 | package com.jason; 2 | 3 | /** 4 | * Created by Jason on 2017-08-29 17:53 5 | */ 6 | 7 | public class VoiceProcessor { 8 | static { 9 | System.loadLibrary("smbPitchShift"); 10 | } 11 | 12 | public static synchronized byte[] dispose(float pitchShift, int sampleRate, int size, byte[] in) { 13 | byte[] out = new byte[size]; 14 | dispose(pitchShift, sampleRate, size, in, out, new float[size / 2], new float[size / 2]); 15 | return out; 16 | } 17 | 18 | private static native void dispose(float pitchShift, int sampleRate, int size, byte[] in, byte[] out, float[] floatIn, float[] floatOut); 19 | } 20 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/Player.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import android.content.Context; 4 | import com.jason.voicelibrary.utils.FileUtil; 5 | import java.io.File; 6 | 7 | /** 8 | * Created by Jason on 2017-08-29 11:54 9 | */ 10 | 11 | public class Player { 12 | private static Player instance; 13 | private String fileName = "temp.wav"; 14 | private PlayerThread playerThread; 15 | private File file; 16 | private float pitchShift; 17 | 18 | private Player() { 19 | } 20 | 21 | public static synchronized Player getInstance() { 22 | if (instance == null) { 23 | instance = new Player(); 24 | } 25 | return instance; 26 | } 27 | 28 | public Player setPitchShift(float pitchShift) { 29 | this.pitchShift = pitchShift; 30 | return instance; 31 | } 32 | 33 | public void start(Context context) { 34 | if (playerThread != null) { 35 | stop(); 36 | } 37 | file = new File(FileUtil.getCacheRootFile(context), fileName); 38 | playerThread = new PlayerThread(file, pitchShift); 39 | playerThread.start(); 40 | } 41 | 42 | public void stop() { 43 | if (playerThread != null) { 44 | playerThread.quit(); 45 | playerThread = null; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/PlayerThread.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioManager; 5 | import android.media.AudioTrack; 6 | import android.util.Log; 7 | 8 | import com.jason.VoiceProcessor; 9 | 10 | import java.io.DataInputStream; 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.FileNotFoundException; 14 | import java.io.IOException; 15 | 16 | /** 17 | * Created by Jason on 2017-08-29 11:57 18 | */ 19 | 20 | public class PlayerThread extends Thread { 21 | private static final String TAG = Player.class.getSimpleName(); 22 | private static final int SAMPLE_RATE_INHZ = 8000; 23 | private boolean running; 24 | private AudioTrack audioTrack; 25 | private int bufSize; 26 | private DataInputStream dis; 27 | private float pitchShift; 28 | 29 | public PlayerThread(File file, float pitchShift) { 30 | this.pitchShift = pitchShift; 31 | bufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); 32 | audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE_INHZ, 33 | AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufSize * 2, AudioTrack.MODE_STREAM); 34 | 35 | try { 36 | dis = new DataInputStream(new FileInputStream(file)); 37 | } catch (FileNotFoundException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | public void run() { 43 | try { 44 | running = true; 45 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); 46 | byte[] tempBuffer = new byte[bufSize]; 47 | int readCount = 0; 48 | while (dis.available() > 0 && running) { 49 | readCount = dis.read(tempBuffer); 50 | if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) { 51 | continue; 52 | } 53 | if (readCount != 0 && readCount != -1) { 54 | audioTrack.play(); 55 | audioTrack.write(pitchShift == 1.0f ? tempBuffer : VoiceProcessor.dispose(pitchShift, SAMPLE_RATE_INHZ, bufSize, tempBuffer), 0, readCount); 56 | } 57 | } 58 | release(); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | public void quit() { 65 | running = false; 66 | } 67 | 68 | private void release() { 69 | audioTrack.stop(); 70 | audioTrack.release(); 71 | audioTrack = null; 72 | try { 73 | dis.close(); 74 | } catch (IOException e) { 75 | e.printStackTrace(); 76 | } 77 | Log.e(TAG, "stop"); 78 | } 79 | 80 | public AudioTrack getAudioTrack() { 81 | return audioTrack; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/RecordListener.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | /** 4 | * Created by Admin on 2017-08-07. 5 | */ 6 | 7 | public interface RecordListener { 8 | public void onComplete(String path); 9 | public void onCancel(); 10 | } 11 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/RecordThread.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioRecord; 5 | import android.media.MediaRecorder; 6 | import android.os.Handler; 7 | import android.os.Message; 8 | import android.util.Log; 9 | import java.io.File; 10 | import java.io.FileNotFoundException; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created by Admin on 2017-08-07. 16 | */ 17 | 18 | public class RecordThread extends Thread { 19 | private static final String TAG = Recorder.class.getSimpleName(); 20 | private static final int SAMPLE_RATE_INHZ = 8000; 21 | private boolean running; 22 | private AudioRecord audioRecord; 23 | private int bufSize; 24 | private byte[] mBytes; 25 | private FileOutputStream os; 26 | private Handler handler; 27 | 28 | public RecordThread(File file, Handler handler) { 29 | this.handler = handler; 30 | bufSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, 31 | AudioFormat.CHANNEL_IN_MONO, 32 | AudioFormat.ENCODING_PCM_16BIT); 33 | audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, 34 | AudioFormat.CHANNEL_IN_MONO, 35 | AudioFormat.ENCODING_PCM_16BIT, bufSize); 36 | mBytes = new byte[bufSize]; 37 | 38 | try { 39 | os = new FileOutputStream(file); 40 | } catch (FileNotFoundException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | public void run() { 46 | try { 47 | running = true; 48 | byte[] bytes_pkg; 49 | audioRecord.startRecording(); 50 | while (running) { 51 | int len = audioRecord.read(mBytes, 0, bufSize); 52 | if (len > 0) { 53 | updataVolume(mBytes); 54 | bytes_pkg = mBytes.clone(); 55 | try { 56 | os.write(bytes_pkg, 0, bytes_pkg.length); 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | } 62 | release(); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | 68 | private void updataVolume(byte[] buffer) { 69 | long v = 0; 70 | // 将 buffer 内容取出,进行平方和运算 71 | for (int i = 0; i < buffer.length; i++) { 72 | v += buffer[i] * buffer[i]; 73 | } 74 | // 平方和除以数据总长度,得到音量大小。 75 | double mean = v / (double) buffer.length; 76 | double volume = 10 * Math.log10(mean); 77 | 78 | Message message = new Message(); 79 | message.obj = volume; 80 | handler.sendMessage(message); 81 | } 82 | 83 | public void quit() { 84 | running = false; 85 | } 86 | 87 | private void release() { 88 | audioRecord.stop(); 89 | audioRecord.release(); 90 | audioRecord = null; 91 | mBytes = null; 92 | try { 93 | os.close(); 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | } 97 | Log.e(TAG, "stop"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/Recorder.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.view.View; 7 | import com.jason.voicelibrary.utils.FileUtil; 8 | import com.jason.voicelibrary.view.RecordViewDialog; 9 | import java.io.File; 10 | 11 | /** 12 | * Created by Admin on 2017-08-07. 13 | */ 14 | 15 | public class Recorder { 16 | private static Recorder instance; 17 | private String fileName = "temp.wav"; 18 | private RecordThread recordThread; 19 | private File file; 20 | private RecordViewDialog recordViewDialog; 21 | private RecordListener recordListener; 22 | 23 | private Recorder() { 24 | } 25 | 26 | public static synchronized Recorder getInstance() { 27 | if (instance == null) { 28 | instance = new Recorder(); 29 | } 30 | return instance; 31 | } 32 | 33 | public Recorder setListener(RecordListener listener) { 34 | recordListener = listener; 35 | return instance; 36 | } 37 | 38 | public void start(Context context) { 39 | if (recordThread != null) { 40 | stop(); 41 | } 42 | file = new File(FileUtil.getCacheRootFile(context), fileName); 43 | recordThread = new RecordThread(file, handler); 44 | recordThread.start(); 45 | 46 | recordViewDialog = new RecordViewDialog(context, R.style.Dialog, onClickListener); 47 | recordViewDialog.show(); 48 | } 49 | 50 | public void stop() { 51 | if (recordThread != null) { 52 | recordThread.quit(); 53 | recordThread = null; 54 | } 55 | handler.postDelayed(new Runnable() { 56 | @Override 57 | public void run() { 58 | if (recordViewDialog != null) recordViewDialog.dismiss(); 59 | } 60 | }, 100); 61 | } 62 | 63 | private View.OnClickListener onClickListener = new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | stop(); 67 | int i = v.getId(); 68 | if (i == R.id.ok) { 69 | recordListener.onComplete(file.getPath()); 70 | } else if (i == R.id.delete) { 71 | new File(FileUtil.getCacheRootFile(v.getContext()), fileName).delete(); 72 | recordListener.onCancel(); 73 | } 74 | } 75 | }; 76 | 77 | private Handler handler = new Handler() { 78 | @Override 79 | public void handleMessage(Message msg) { 80 | super.handleMessage(msg); 81 | if (recordThread == null) return; 82 | if (recordViewDialog != null) { 83 | try { 84 | recordViewDialog.setVolume(Integer.parseInt(new java.text.DecimalFormat("0").format(msg.obj))); 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary.utils; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import java.io.File; 6 | 7 | /** 8 | * Created by Admin on 2017-08-08. 9 | */ 10 | 11 | public class FileUtil { 12 | private static String Folder = "Recorder"; 13 | 14 | public static File getCacheRootFile(Context context) { 15 | File cacheRootDir = null; 16 | // 判断sd卡是否存在 17 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 18 | cacheRootDir = new File(Environment.getExternalStorageDirectory() + "/" + context.getPackageName(), Folder);// 获取根目录 19 | } else { 20 | cacheRootDir = new File(context.getPackageName(), Folder); 21 | } 22 | if (!cacheRootDir.exists()) { 23 | cacheRootDir.mkdirs();// 如果路径不存在就先创建路径 24 | } 25 | return cacheRootDir; 26 | } 27 | 28 | public static void deleteDirWihtFile(File dir) { 29 | if (dir == null || !dir.exists() || !dir.isDirectory()) 30 | return; 31 | for (File file : dir.listFiles()) { 32 | if (file.isFile()) 33 | file.delete(); // 删除所有文件 34 | else if (file.isDirectory()) 35 | deleteDirWihtFile(file); // 递规的方式删除文件夹 36 | } 37 | dir.delete();// 删除目录本身 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/view/RecordViewDialog.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary.view; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import android.view.Display; 8 | import android.view.Gravity; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.Window; 12 | import android.view.WindowManager; 13 | import android.widget.Chronometer; 14 | import android.widget.ImageView; 15 | 16 | import com.jason.voicelibrary.R; 17 | 18 | /** 19 | * Created by Admin on 2017-08-07. 20 | */ 21 | 22 | public class RecordViewDialog extends Dialog { 23 | private Context context; 24 | private VoiceLineView voicLine; 25 | private ImageView ok, delete; 26 | private Chronometer chronometer; 27 | private View.OnClickListener listener; 28 | 29 | public RecordViewDialog(Context context, int theme, View.OnClickListener listener) { 30 | super(context, theme); 31 | this.context = context; 32 | this.listener = listener; 33 | } 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | init(); 39 | this.setCancelable(false); 40 | } 41 | 42 | @Override 43 | public void show() { 44 | super.show(); 45 | chronometer.start(); 46 | } 47 | 48 | @Override 49 | public void dismiss() { 50 | super.dismiss(); 51 | chronometer.stop(); 52 | } 53 | 54 | public void setVolume(int volume) { 55 | voicLine.setVolume(volume); 56 | } 57 | 58 | private void init() { 59 | View view = LayoutInflater.from(context).inflate(R.layout.record_view_dialog, null); 60 | setContentView(view); 61 | voicLine = (VoiceLineView) view.findViewById(R.id.voicLine); 62 | ok = (ImageView) view.findViewById(R.id.ok); 63 | delete = (ImageView) view.findViewById(R.id.delete); 64 | chronometer = (Chronometer) view.findViewById(R.id.chronometer); 65 | ok.setOnClickListener(listener); 66 | delete.setOnClickListener(listener); 67 | //设置dialog大小,这里是一个小赠送,模块好的控件大小设置 68 | Window dialogWindow = getWindow(); 69 | WindowManager manager = ((Activity) context).getWindowManager(); 70 | WindowManager.LayoutParams params = dialogWindow.getAttributes(); // 获取对话框当前的参数值 71 | dialogWindow.setGravity(Gravity.CENTER);//设置对话框位置 72 | Display d = manager.getDefaultDisplay(); // 获取屏幕宽、高度 73 | params.width = (int) (d.getWidth() * 0.45); // 宽度设置为屏幕的0.65,根据实际情况调整 74 | dialogWindow.setAttributes(params); 75 | } 76 | } -------------------------------------------------------------------------------- /Voicelibrary/src/main/java/com/jason/voicelibrary/view/VoiceLineView.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | import com.jason.voicelibrary.R; 14 | 15 | import java.util.ArrayList; 16 | import java.util.LinkedList; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by carlos on 2016/1/29. 21 | * 自定义声音振动曲线view 22 | */ 23 | public class VoiceLineView extends View { 24 | private final int LINE = 0; 25 | private final int RECT = 1; 26 | 27 | private int middleLineColor = Color.BLACK; 28 | private int voiceLineColor = Color.BLACK; 29 | private float middleLineHeight = 4; 30 | private Paint paint; 31 | private Paint paintVoicLine; 32 | private int mode; 33 | /** 34 | * 灵敏度 35 | */ 36 | private int sensibility = 4; 37 | 38 | private float maxVolume = 100; 39 | 40 | 41 | private float translateX = 0; 42 | private boolean isSet = false; 43 | 44 | /** 45 | * 振幅 46 | */ 47 | private float amplitude = 1; 48 | /** 49 | * 音量 50 | */ 51 | private float volume = 10; 52 | private int fineness = 1; 53 | private float targetVolume = 1; 54 | 55 | 56 | private long speedY = 50; 57 | private float rectWidth = 25; 58 | private float rectSpace = 5; 59 | private float rectInitHeight = 4; 60 | private List rectList; 61 | 62 | private long lastTime = 0; 63 | private int lineSpeed = 90; 64 | 65 | List paths = null; 66 | 67 | public VoiceLineView(Context context) { 68 | super(context); 69 | } 70 | 71 | public VoiceLineView(Context context, AttributeSet attrs) { 72 | super(context, attrs); 73 | initAtts(context, attrs); 74 | } 75 | 76 | public VoiceLineView(Context context, AttributeSet attrs, int defStyleAttr) { 77 | super(context, attrs, defStyleAttr); 78 | initAtts(context, attrs); 79 | } 80 | 81 | private void initAtts(Context context, AttributeSet attrs) { 82 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.voiceView); 83 | mode = typedArray.getInt(R.styleable.voiceView_viewMode, 0); 84 | voiceLineColor = typedArray.getColor(R.styleable.voiceView_voiceLine, Color.BLACK); 85 | maxVolume = typedArray.getFloat(R.styleable.voiceView_maxVolume, 100); 86 | sensibility = typedArray.getInt(R.styleable.voiceView_sensibility, 4); 87 | if (mode == RECT) { 88 | rectWidth = typedArray.getDimension(R.styleable.voiceView_rectWidth, 25); 89 | rectSpace = typedArray.getDimension(R.styleable.voiceView_rectSpace, 5); 90 | rectInitHeight = typedArray.getDimension(R.styleable.voiceView_rectInitHeight, 4); 91 | } else { 92 | middleLineColor = typedArray.getColor(R.styleable.voiceView_middleLine, Color.BLACK); 93 | middleLineHeight = typedArray.getDimension(R.styleable.voiceView_middleLineHeight, 4); 94 | lineSpeed = typedArray.getInt(R.styleable.voiceView_lineSpeed, 90); 95 | fineness = typedArray.getInt(R.styleable.voiceView_fineness, 1); 96 | paths = new ArrayList<>(20); 97 | for (int i = 0; i < 20; i++) { 98 | paths.add(new Path()); 99 | } 100 | } 101 | typedArray.recycle(); 102 | } 103 | 104 | @Override 105 | protected void onDraw(Canvas canvas) { 106 | if (mode == RECT) { 107 | drawVoiceRect(canvas); 108 | } else { 109 | drawMiddleLine(canvas); 110 | drawVoiceLine(canvas); 111 | } 112 | run(); 113 | } 114 | 115 | private void drawMiddleLine(Canvas canvas) { 116 | if (paint == null) { 117 | paint = new Paint(); 118 | paint.setColor(middleLineColor); 119 | paint.setAntiAlias(true); 120 | } 121 | canvas.save(); 122 | canvas.drawRect(0, getHeight() / 2 - middleLineHeight / 2, getWidth(), getHeight() / 2 + middleLineHeight / 2, paint); 123 | canvas.restore(); 124 | } 125 | 126 | private void drawVoiceLine(Canvas canvas) { 127 | lineChange(); 128 | if (paintVoicLine == null) { 129 | paintVoicLine = new Paint(); 130 | paintVoicLine.setColor(voiceLineColor); 131 | paintVoicLine.setAntiAlias(true); 132 | paintVoicLine.setStyle(Paint.Style.STROKE); 133 | paintVoicLine.setStrokeWidth(2); 134 | } 135 | canvas.save(); 136 | int moveY = getHeight() / 2; 137 | for (int i = 0; i < paths.size(); i++) { 138 | paths.get(i).reset(); 139 | paths.get(i).moveTo(getWidth(), getHeight() / 2); 140 | } 141 | for (float i = getWidth() - 1; i >= 0; i -= fineness) { 142 | amplitude = 4 * volume * i / getWidth() - 4 * volume * i * i / getWidth() / getWidth(); 143 | for (int n = 1; n <= paths.size(); n++) { 144 | float sin = amplitude * (float) Math.sin((i - Math.pow(1.22, n)) * Math.PI / 180 - translateX); 145 | paths.get(n - 1).lineTo(i, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY)); 146 | } 147 | } 148 | for (int n = 0; n < paths.size(); n++) { 149 | if (n == paths.size() - 1) { 150 | paintVoicLine.setAlpha(255); 151 | } else { 152 | paintVoicLine.setAlpha(n * 130 / paths.size()); 153 | } 154 | if (paintVoicLine.getAlpha() > 0) { 155 | canvas.drawPath(paths.get(n), paintVoicLine); 156 | } 157 | } 158 | canvas.restore(); 159 | } 160 | 161 | private void drawVoiceRect(Canvas canvas) { 162 | if (paintVoicLine == null) { 163 | paintVoicLine = new Paint(); 164 | paintVoicLine.setColor(voiceLineColor); 165 | paintVoicLine.setAntiAlias(true); 166 | paintVoicLine.setStyle(Paint.Style.STROKE); 167 | paintVoicLine.setStrokeWidth(2); 168 | } 169 | if (rectList == null) { 170 | rectList = new LinkedList<>(); 171 | } 172 | int totalWidth = (int) (rectSpace + rectWidth); 173 | if (speedY % totalWidth < 6) { 174 | Rect rect = new Rect((int) (-rectWidth - 10 - speedY + speedY % totalWidth), 175 | (int) (getHeight() / 2 - rectInitHeight / 2 - (volume == 10 ? 0 : volume / 2)), 176 | (int) (-10 - speedY + speedY % totalWidth), 177 | (int) (getHeight() / 2 + rectInitHeight / 2 + (volume == 10 ? 0 : volume / 2))); 178 | if (rectList.size() > getWidth() / (rectSpace + rectWidth) + 2) { 179 | rectList.remove(0); 180 | } 181 | rectList.add(rect); 182 | } 183 | canvas.translate(speedY, 0); 184 | for (int i = rectList.size() - 1; i >= 0; i--) { 185 | canvas.drawRect(rectList.get(i), paintVoicLine); 186 | } 187 | rectChange(); 188 | } 189 | 190 | public void setVolume(int volume) { 191 | if (volume > maxVolume * sensibility / 25) { 192 | isSet = true; 193 | this.targetVolume = getHeight() * volume / 2 / maxVolume; 194 | } 195 | } 196 | 197 | private void lineChange() { 198 | if (lastTime == 0) { 199 | lastTime = System.currentTimeMillis(); 200 | translateX += 1.5; 201 | } else { 202 | if (System.currentTimeMillis() - lastTime > lineSpeed) { 203 | lastTime = System.currentTimeMillis(); 204 | translateX += 1.5; 205 | } else { 206 | return; 207 | } 208 | } 209 | if (volume < targetVolume && isSet) { 210 | volume += getHeight() / 30; 211 | } else { 212 | isSet = false; 213 | if (volume <= 10) { 214 | volume = 10; 215 | } else { 216 | if (volume < getHeight() / 30) { 217 | volume -= getHeight() / 60; 218 | } else { 219 | volume -= getHeight() / 30; 220 | } 221 | } 222 | } 223 | } 224 | 225 | private void rectChange() { 226 | speedY += 6; 227 | if (volume < targetVolume && isSet) { 228 | volume += getHeight() / 30; 229 | } else { 230 | isSet = false; 231 | if (volume <= 10) { 232 | volume = 10; 233 | } else { 234 | if (volume < getHeight() / 30) { 235 | volume -= getHeight() / 60; 236 | } else { 237 | volume -= getHeight() / 30; 238 | } 239 | } 240 | } 241 | } 242 | 243 | public void run() { 244 | if (mode == RECT) { 245 | postInvalidateDelayed(30); 246 | } else { 247 | invalidate(); 248 | } 249 | } 250 | } -------------------------------------------------------------------------------- /Voicelibrary/src/main/jniLibs/armeabi-v7a/libsmbPitchShift.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-LiuHuan/MyVoice/58b9d7539245d8755cdf209dc85787bbaca4d9c0/Voicelibrary/src/main/jniLibs/armeabi-v7a/libsmbPitchShift.so -------------------------------------------------------------------------------- /Voicelibrary/src/main/jniLibs/armeabi/libsmbPitchShift.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-LiuHuan/MyVoice/58b9d7539245d8755cdf209dc85787bbaca4d9c0/Voicelibrary/src/main/jniLibs/armeabi/libsmbPitchShift.so -------------------------------------------------------------------------------- /Voicelibrary/src/main/jniLibs/x86/libsmbPitchShift.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-LiuHuan/MyVoice/58b9d7539245d8755cdf209dc85787bbaca4d9c0/Voicelibrary/src/main/jniLibs/x86/libsmbPitchShift.so -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/drawable/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Android-LiuHuan/MyVoice/58b9d7539245d8755cdf209dc85787bbaca4d9c0/Voicelibrary/src/main/res/drawable/delete.png -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/drawable/hollow_circle_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/drawable/hollow_circle_click.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/drawable/hollow_circle_p_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/drawable/shape_circular_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/layout/record_view_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 30 | 31 | 32 | 40 | 41 | 44 | 45 | 58 | 59 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/values/attrs.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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RecordLibrary 3 | 4 | -------------------------------------------------------------------------------- /Voicelibrary/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /Voicelibrary/src/test/java/com/jason/voicelibrary/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jason.voicelibrary; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | def globalConfig = rootProject.ext.android 5 | compileSdkVersion globalConfig.compileSdkVersion as int 6 | buildToolsVersion globalConfig.buildToolsVersion as String 7 | 8 | defaultConfig { 9 | applicationId "com.jason.voice" 10 | minSdkVersion globalConfig.minSdkVersion as int 11 | targetSdkVersion globalConfig.targetSdkVersion as int 12 | versionCode globalConfig.versionCode as int 13 | versionName globalConfig.versionName as String 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:appcompat-v7:25.3.1' 31 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 32 | testCompile 'junit:junit:4.12' 33 | compile project(path: ':Voicelibrary') 34 | } 35 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Admin\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jason/voice/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jason.voice; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jason.mue", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/jason/voice/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jason.voice; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.SeekBar; 8 | import android.widget.TextView; 9 | import com.jason.voicelibrary.Player; 10 | import com.jason.voicelibrary.Recorder; 11 | import com.jason.voicelibrary.RecordListener; 12 | 13 | public class MainActivity extends AppCompatActivity implements View.OnClickListener, RecordListener { 14 | private TextView tv, tv2; 15 | private Button btn1, btn2; 16 | private SeekBar seekBar; 17 | private float pitchShift = 1; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | tv = (TextView) findViewById(R.id.tv); 25 | tv2 = (TextView) findViewById(R.id.tv2); 26 | btn1 = (Button) findViewById(R.id.btn1); 27 | btn2 = (Button) findViewById(R.id.btn2); 28 | seekBar = (SeekBar) findViewById(R.id.seekBar); 29 | btn1.setOnClickListener(this); 30 | btn2.setOnClickListener(this); 31 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 32 | @Override 33 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 34 | pitchShift = (float) progress / 100; 35 | tv2.setText(String.valueOf(pitchShift)); 36 | } 37 | 38 | @Override 39 | public void onStartTrackingTouch(SeekBar seekBar) { 40 | } 41 | 42 | @Override 43 | public void onStopTrackingTouch(SeekBar seekBar) { 44 | } 45 | }); 46 | } 47 | 48 | @Override 49 | public void onClick(View v) { 50 | switch (v.getId()) { 51 | case R.id.btn1: 52 | Recorder.getInstance().setListener(this).start(this); 53 | break; 54 | case R.id.btn2: 55 | Player.getInstance().setPitchShift(pitchShift).start(this); 56 | break; 57 | } 58 | } 59 | 60 | @Override 61 | public void onComplete(String path) { 62 | tv.setText("path:" + path); 63 | } 64 | 65 | @Override 66 | public void onCancel() { 67 | tv.setText("path:"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 27 | 28 |