├── .gitignore ├── .idea └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── asha │ │ └── libresample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── asha │ │ │ └── libresample │ │ │ ├── MainActivity.java │ │ │ ├── PcmPlayer.java │ │ │ ├── RecActivity.java │ │ │ └── RecAudioClient.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_record.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ └── asha │ └── libresample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libresample2 ├── .gitignore ├── CMakeLists.txt ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── asha │ │ └── libresample2 │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── asha │ │ │ └── libresample2 │ │ │ └── Resample.java │ ├── jni │ │ ├── LICENSE │ │ ├── resample.c │ │ ├── resample.h │ │ └── resample_jni.c │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── asha │ └── libresample2 │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Asha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-libresample 2 | android libresample, based on https://github.com/intervigilium/libresample 3 | 4 | ## Gradle 5 | ```java 6 | allprojects { 7 | repositories { 8 | ... 9 | maven { url 'https://jitpack.io' } 10 | } 11 | } 12 | ``` 13 | 14 | ```java 15 | dependencies { 16 | implementation 'com.github.ashqal:android-libresample:0.3.0' 17 | } 18 | ``` 19 | 20 | ## Usage 21 | * create 22 | ```java 23 | Resample resample = new Resample(); 24 | resample.create(48000, 16000, 2048, 1); 25 | ``` 26 | 27 | * destroy 28 | ```java 29 | Resample resample = new Resample(); 30 | resample.destroy(); 31 | ``` 32 | 33 | * resample 34 | ```java 35 | // input is ByteBuffer allocateDirect 36 | // output is ByteBuffer allocateDirect 37 | 38 | int output_len = resample.resample(input, output, input.remaining()); 39 | 40 | // usage of output 41 | cached.put(output.array(), output.arrayOffset(), output_len); 42 | ``` 43 | 44 | * resampleEx 45 | ```java 46 | // input is ByteBuffer allocateDirect 47 | // output is ByteBuffer allocateDirect 48 | 49 | int output_len = resample.resampleEx(input, output, input.remaining()); 50 | 51 | // usage of output 52 | cached.put(output.array(), output.arrayOffset(), output_len); 53 | ``` 54 | 55 | ## License 56 | ``` 57 | MIT License 58 | 59 | Copyright (c) 2018 Asha 60 | 61 | Permission is hereby granted, free of charge, to any person obtaining a copy 62 | of this software and associated documentation files (the "Software"), to deal 63 | in the Software without restriction, including without limitation the rights 64 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 65 | copies of the Software, and to permit persons to whom the Software is 66 | furnished to do so, subject to the following conditions: 67 | 68 | The above copyright notice and this permission notice shall be included in all 69 | copies or substantial portions of the Software. 70 | 71 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 72 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | SOFTWARE. 78 | ``` 79 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | signingConfigs { 5 | } 6 | compileSdkVersion 27 7 | defaultConfig { 8 | applicationId "com.asha.libresample" 9 | minSdkVersion 16 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(include: ['*.jar'], dir: 'libs') 25 | implementation 'com.android.support:appcompat-v7:27.1.1' 26 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 27 | testImplementation 'junit:junit:4.12' 28 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 29 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 30 | implementation project(':libresample2') 31 | } 32 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/asha/libresample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.asha.libresample; 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 | * Instrumented 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() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.netease.libresample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/asha/libresample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.asha.libresample; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.widget.Toast; 11 | 12 | import static android.content.pm.PackageManager.PERMISSION_DENIED; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private View record; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | 23 | record = findViewById(R.id.record); 24 | record.setEnabled(false); 25 | record.setOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(View v) { 28 | Intent i = new Intent(v.getContext(), RecActivity.class); 29 | v.getContext().startActivity(i); 30 | } 31 | }); 32 | 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 34 | requestPermissions(new String[]{ 35 | Manifest.permission.RECORD_AUDIO 36 | }, 1000); 37 | } else { 38 | onPermissionOk(); 39 | } 40 | } 41 | 42 | @Override 43 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 44 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 45 | if (requestCode != 1000) { 46 | return; 47 | } 48 | 49 | for (int k : grantResults) { 50 | if (k == PERMISSION_DENIED) { 51 | onPermissionDenied(); 52 | return; 53 | } 54 | } 55 | 56 | onPermissionOk(); 57 | } 58 | 59 | private void onPermissionDenied() { 60 | Toast.makeText(this, "无权限", Toast.LENGTH_SHORT).show(); 61 | } 62 | 63 | private void onPermissionOk() { 64 | record.setEnabled(true); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/asha/libresample/PcmPlayer.java: -------------------------------------------------------------------------------- 1 | package com.asha.libresample; 2 | 3 | import android.content.Context; 4 | import android.media.AudioFormat; 5 | import android.media.AudioTrack; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * Created by lake on 22/06/16. 11 | * 12 | */ 13 | 14 | public class PcmPlayer { 15 | private AudioTrack trackplayer; 16 | 17 | public PcmPlayer() { 18 | 19 | } 20 | 21 | public void prepare(Context context, int streamType) { 22 | int bufsize = AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, 23 | AudioFormat.ENCODING_PCM_16BIT); 24 | 25 | trackplayer = new AudioTrack(streamType, 26 | 16000, 27 | AudioFormat.CHANNEL_OUT_MONO, 28 | AudioFormat.ENCODING_PCM_16BIT, 29 | bufsize, 30 | AudioTrack.MODE_STREAM); 31 | } 32 | 33 | public void write(byte[] data, int offset, int len) { 34 | trackplayer.write(data, offset, len); 35 | } 36 | 37 | public void start() { 38 | trackplayer.play(); 39 | } 40 | 41 | public void destroy() { 42 | trackplayer.stop(); 43 | trackplayer.release(); 44 | } 45 | 46 | public void pause() { 47 | trackplayer.pause(); 48 | trackplayer.flush(); 49 | } 50 | 51 | public void resume() { 52 | trackplayer.play(); 53 | } 54 | 55 | public void write(ByteBuffer buffer) { 56 | int remain = buffer.remaining(); 57 | buffer.get(buffer.array(), buffer.arrayOffset(), remain); 58 | trackplayer.write(buffer.array(), buffer.arrayOffset(), remain); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/asha/libresample/RecActivity.java: -------------------------------------------------------------------------------- 1 | package com.asha.libresample; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | import android.media.AudioRecord; 6 | import android.media.MediaRecorder; 7 | import android.os.Bundle; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.util.Log; 10 | import android.widget.TextView; 11 | 12 | import com.asha.libresample2.Resample; 13 | 14 | import java.nio.ByteBuffer; 15 | 16 | public class RecActivity extends AppCompatActivity { 17 | 18 | private static final String TAG = "RecActivity"; 19 | 20 | private ByteBuffer cached; 21 | 22 | private ByteBuffer processed; 23 | 24 | private RecAudioClient client; 25 | 26 | private PcmPlayer pcmPlayer; 27 | 28 | private State state = State.Recording; 29 | 30 | private TextView tv; 31 | 32 | private Resample resample; 33 | 34 | private enum State { 35 | Recording, 36 | Playing, 37 | } 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_record); 43 | client = new RecAudioClient(); 44 | pcmPlayer = new PcmPlayer(); 45 | cached = ByteBuffer.allocateDirect(1024 * 100); 46 | processed = ByteBuffer.allocateDirect(4096); 47 | 48 | resample = new Resample(); 49 | resample.create(48000, 16000, 2048, 1); 50 | 51 | final AudioManager audioManager = ((AudioManager) getSystemService(Context.AUDIO_SERVICE)); 52 | audioManager.setSpeakerphoneOn(true); 53 | 54 | tv = findViewById(R.id.sample_text); 55 | record(); 56 | } 57 | 58 | private void record() { 59 | pcmPlayer.prepare(this, AudioManager.STREAM_MUSIC); 60 | 61 | client.prepare(MediaRecorder.AudioSource.DEFAULT, new RecAudioClient.AudioCallback() { 62 | @Override 63 | public void onRecvAudioData(ByteBuffer buffer) { 64 | buffer.reset(); 65 | switch (state) { 66 | case Recording: 67 | if (cached.remaining() >= buffer.remaining()) { 68 | int num = resample.resampleEx(buffer, processed, buffer.remaining()); 69 | Log.w(TAG, String.format("input size:%d output size:%d", buffer.limit(), num)); 70 | cached.put(processed.array(), processed.arrayOffset(), num); 71 | setText(String.format("正在录音:%d/%d", cached.position(), cached.limit())); 72 | } else { 73 | cached.flip(); 74 | cached.mark(); 75 | state = State.Playing; 76 | } 77 | break; 78 | case Playing: 79 | int limit = Math.min(buffer.remaining(), cached.remaining()); 80 | if (limit > 0) { 81 | pcmPlayer.write(cached.array(), cached.arrayOffset() + cached.position(), limit); 82 | cached.position(cached.position() + limit); 83 | setText(String.format("正在播放:%d/%d", cached.position(), cached.limit())); 84 | } else { 85 | cached.clear(); 86 | state = State.Recording; 87 | } 88 | break; 89 | } 90 | } 91 | 92 | @Override 93 | public void onRecStarted(AudioRecord audioRecord) {} 94 | 95 | @Override 96 | public void onRecStopped() {} 97 | 98 | @Override 99 | public void onRecError(String s) {} 100 | }); 101 | } 102 | 103 | @Override 104 | public void onDestroy() { 105 | super.onDestroy(); 106 | resample.destroy(); 107 | client.destroy(); 108 | pcmPlayer.destroy(); 109 | } 110 | 111 | @Override 112 | public void onPause() { 113 | super.onPause(); 114 | client.pause(); 115 | pcmPlayer.pause(); 116 | } 117 | 118 | @Override 119 | public void onResume() { 120 | super.onResume(); 121 | client.resume(); 122 | pcmPlayer.resume(); 123 | } 124 | 125 | private void setText(final String format) { 126 | tv.post(new Runnable() { 127 | @Override 128 | public void run() { 129 | tv.setText(format); 130 | } 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/asha/libresample/RecAudioClient.java: -------------------------------------------------------------------------------- 1 | package com.asha.libresample; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioRecord; 5 | import android.util.Log; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * Created by lake on 16-5-24. 11 | * 12 | */ 13 | public class RecAudioClient { 14 | private static final String TAG = "SOUNDTOUCH"; 15 | private final Object syncOp = new Object(); 16 | private AudioRecordThread audioRecordThread; 17 | private AudioRecord audioRecord; 18 | private ByteBuffer audioBuffer; 19 | private int audioRecordBufferSize; 20 | private int audioRecordSliceSize; 21 | private AudioCallback callback; 22 | private static final int sSampleRate = 48000; 23 | 24 | public RecAudioClient() { 25 | } 26 | 27 | public int prepare(int audioSource, AudioCallback callback) { 28 | synchronized (syncOp) { 29 | this.callback = callback; 30 | audioRecordSliceSize = 2048; 31 | audioRecordBufferSize = audioRecordSliceSize; 32 | return prepareAudio(audioSource); 33 | } 34 | } 35 | 36 | private boolean start() { 37 | synchronized (syncOp) { 38 | if (audioRecordThread != null && audioRecordThread.isRunning) { 39 | return true; 40 | } 41 | 42 | audioRecord.startRecording(); 43 | audioRecordThread = new AudioRecordThread(); 44 | audioRecordThread.start(); 45 | 46 | if (callback != null) { 47 | callback.onRecStarted(audioRecord); 48 | } 49 | return true; 50 | } 51 | } 52 | 53 | private boolean stop() { 54 | synchronized (syncOp) { 55 | audioRecordThread.quit(); 56 | try { 57 | audioRecordThread.join(); 58 | } catch (InterruptedException ignored) { 59 | } 60 | audioRecordThread = null; 61 | audioRecord.stop(); 62 | if (callback != null) { 63 | callback.onRecStopped(); 64 | } 65 | return true; 66 | } 67 | } 68 | 69 | public boolean destroy() { 70 | synchronized (syncOp) { 71 | audioRecord.release(); 72 | return true; 73 | } 74 | } 75 | 76 | private int prepareAudio(int audioSource) { 77 | int minBufferSize = AudioRecord.getMinBufferSize(sSampleRate, 78 | AudioFormat.CHANNEL_IN_MONO, 79 | AudioFormat.ENCODING_PCM_16BIT); 80 | audioRecord = new AudioRecord(audioSource, 81 | sSampleRate, 82 | AudioFormat.CHANNEL_IN_MONO, 83 | AudioFormat.ENCODING_PCM_16BIT, 84 | minBufferSize * 5); 85 | // audioRecordBufferSize 86 | audioBuffer = ByteBuffer.allocateDirect(audioRecordBufferSize * 2); 87 | if (AudioRecord.STATE_INITIALIZED != audioRecord.getState()) { 88 | Log.e("aa", "audioRecord.getState()!=AudioRecord.STATE_INITIALIZED!"); 89 | return -1; 90 | } 91 | if (AudioRecord.SUCCESS != audioRecord.setPositionNotificationPeriod(audioRecordSliceSize)) { 92 | Log.e("aa", "AudioRecord.SUCCESS != audioRecord.setPositionNotificationPeriod(" + audioRecordSliceSize + ")"); 93 | return -1; 94 | } 95 | return audioRecord.getAudioSessionId(); 96 | } 97 | 98 | public void resume() { 99 | start(); 100 | } 101 | 102 | public void pause() { 103 | stop(); 104 | } 105 | 106 | private class AudioRecordThread extends Thread { 107 | private boolean isRunning = true; 108 | 109 | AudioRecordThread() { 110 | isRunning = true; 111 | } 112 | 113 | public void quit() { 114 | isRunning = false; 115 | } 116 | 117 | @Override 118 | public void run() { 119 | while (isRunning) { 120 | int size = audioRecord.read(audioBuffer.array(), audioBuffer.arrayOffset(), audioRecordBufferSize); 121 | 122 | if (!isRunning) { 123 | return; 124 | } 125 | 126 | if (size > 0) { 127 | audioBuffer.clear(); 128 | audioBuffer.put(audioBuffer.array(), audioBuffer.arrayOffset(), audioRecordBufferSize); 129 | audioBuffer.flip(); 130 | audioBuffer.mark(); 131 | callback.onRecvAudioData(audioBuffer); 132 | } else { 133 | callback.onRecError("size 0!"); 134 | } 135 | } 136 | } 137 | } 138 | 139 | public interface AudioCallback { 140 | void onRecvAudioData(ByteBuffer audioBuffer); 141 | void onRecStarted(AudioRecord audioRecord); 142 | void onRecStopped(); 143 | void onRecError(String s); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |