diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..e449e74 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7158618 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4f7c749 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c286fc6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.0" + defaultConfig { + applicationId "tech.oom.idealrecorderdemo" + minSdkVersion 16 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + testCompile 'junit:junit:4.12' + compile 'com.yanzhenjie:permission:1.1.0' + compile 'com.github.Jay-Goo:WaveLineView:v1.0.3' + compile project(':library') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ba6c74c --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\tools\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/tech/oom/idealrecorder/ExampleInstrumentedTest.java b/app/src/androidTest/java/tech/oom/idealrecorder/ExampleInstrumentedTest.java new file mode 100644 index 0000000..23fe574 --- /dev/null +++ b/app/src/androidTest/java/tech/oom/idealrecorder/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package tech.oom.idealrecorder; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("tech.oom.idealrecorder", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..35a9450 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/tech/oom/idealrecorderdemo/MainActivity.java b/app/src/main/java/tech/oom/idealrecorderdemo/MainActivity.java new file mode 100644 index 0000000..efc2be6 --- /dev/null +++ b/app/src/main/java/tech/oom/idealrecorderdemo/MainActivity.java @@ -0,0 +1,212 @@ +package tech.oom.idealrecorderdemo; + +import android.content.DialogInterface; +import android.media.AudioFormat; +import android.media.MediaRecorder; +import android.os.Bundle; +import android.os.Environment; +import android.support.v7.app.AppCompatActivity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import com.yanzhenjie.permission.AndPermission; +import com.yanzhenjie.permission.Permission; +import com.yanzhenjie.permission.PermissionListener; +import com.yanzhenjie.permission.Rationale; +import com.yanzhenjie.permission.RationaleListener; + +import java.io.File; +import java.util.List; + +import jaygoo.widget.wlv.WaveLineView; +import tech.oom.idealrecorder.IdealRecorder; +import tech.oom.idealrecorder.StatusListener; +import tech.oom.idealrecorderdemo.widget.WaveView; + +public class MainActivity extends AppCompatActivity { + + private Button recordBtn; + private WaveView waveView; + private WaveLineView waveLineView; + private TextView tips; + /** + * IdealRecorder的实例 + */ + private IdealRecorder idealRecorder; + /** + * Recorder的配置信息 采样率 采样位数 + */ + private IdealRecorder.RecordConfig recordConfig; + + private RationaleListener rationaleListener = new RationaleListener() { + @Override + public void showRequestPermissionRationale(int requestCode, final Rationale rationale) { + com.yanzhenjie.alertdialog.AlertDialog.newBuilder(MainActivity.this) + .setTitle("友好提醒") + .setMessage("录制声纹锁需要录音读取文件相关权限哦,爱给不给") + .setPositiveButton("好,给你", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + rationale.resume(); + } + }).setNegativeButton("我是拒绝的", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + rationale.cancel(); + } + }).create().show(); + } + }; + + + private StatusListener statusListener = new StatusListener() { + @Override + public void onStartRecording() { + waveLineView.startAnim(); + tips.setText("开始录音"); + } + + @Override + public void onRecordData(short[] data, int length) { + + for (int i = 0; i < length; i += 60) { + waveView.addData(data[i]); + } + } + + @Override + public void onVoiceVolume(int volume) { + double myVolume = (volume - 40) * 4; + waveLineView.setVolume((int) myVolume); + } + + @Override + public void onRecordError(int code, String errorMsg) { + tips.setText("录音错误" + errorMsg); + } + + @Override + public void onFileSaveFailed(String error) { + Toast.makeText(MainActivity.this, "文件保存失败", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onFileSaveSuccess(String fileUri) { + Toast.makeText(MainActivity.this, "文件保存成功,路径是" + fileUri, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onStopRecording() { + tips.setText("录音结束"); + waveLineView.stopAnim(); + } + }; + + + private PermissionListener listener = new PermissionListener() { + @Override + public void onSucceed(int requestCode, List grantedPermissions) { + + if (requestCode == 100) { + record(); + } + } + + @Override + public void onFailed(int requestCode, List deniedPermissions) { + // 权限申请失败回调。 + if (requestCode == 100) { + Toast.makeText(MainActivity.this, "没有录音和文件读取权限,你自己看着办", Toast.LENGTH_SHORT).show(); + } + if (AndPermission.hasAlwaysDeniedPermission(MainActivity.this, deniedPermissions)) { + AndPermission.defaultSettingDialog(MainActivity.this, 300).show(); + } + } + + }; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + recordBtn = (Button) findViewById(R.id.register_record_btn); + waveView = (WaveView) findViewById(R.id.wave_view); + waveLineView = (WaveLineView) findViewById(R.id.waveLineView); + tips = (TextView) findViewById(R.id.tips); + idealRecorder = IdealRecorder.getInstance(); + recordBtn.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + readyRecord(); + return true; + } + }); + recordBtn.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_UP: + stopRecord(); + return false; + + } + return false; + } + }); + recordConfig = new IdealRecorder.RecordConfig(MediaRecorder.AudioSource.MIC, IdealRecorder.RecordConfig.SAMPLE_RATE_22K_HZ, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); + + } + + /** + * 准备录音 录音之前 先判断是否有相关权限 + */ + private void readyRecord() { + AndPermission.with(this) + .requestCode(100) + .permission(Permission.MICROPHONE, Permission.STORAGE) + .rationale(rationaleListener).callback(listener).start(); + + } + + /** + * 开始录音 + */ + private void record() { + //如果需要保存录音文件 设置好保存路径就会自动保存 也可以通过onRecordData 回调自己保存 不设置 不会保存录音 + idealRecorder.setRecordFilePath(getSaveFilePath()); + //设置录音配置 最长录音时长 以及音量回调的时间间隔 + idealRecorder.setRecordConfig(recordConfig).setMaxRecordTime(20000).setVolumeInterval(200); + //设置录音时各种状态的监听 + idealRecorder.setStatusListener(statusListener); + idealRecorder.start(); //开始录音 + + } + + /** + * 获取文件保存路径 + * + * @return + */ + private String getSaveFilePath() { + File file = new File(Environment.getExternalStorageDirectory(), "Audio"); + if (!file.exists()) { + file.mkdirs(); + } + File wavFile = new File(file, "ideal.wav"); + return wavFile.getAbsolutePath(); + } + + + /** + * 停止录音 + */ + private void stopRecord() { + //停止录音 + idealRecorder.stop(); + } +} \ No newline at end of file diff --git a/app/src/main/java/tech/oom/idealrecorderdemo/MyApplication.java b/app/src/main/java/tech/oom/idealrecorderdemo/MyApplication.java new file mode 100644 index 0000000..5f33cfd --- /dev/null +++ b/app/src/main/java/tech/oom/idealrecorderdemo/MyApplication.java @@ -0,0 +1,14 @@ +package tech.oom.idealrecorderdemo; + +import android.app.Application; + +import tech.oom.idealrecorder.IdealRecorder; + + +public class MyApplication extends Application { + @Override + public void onCreate() { + super.onCreate(); + IdealRecorder.init(this); + } +} diff --git a/app/src/main/java/tech/oom/idealrecorderdemo/widget/WaveView.java b/app/src/main/java/tech/oom/idealrecorderdemo/widget/WaveView.java new file mode 100644 index 0000000..5e383fe --- /dev/null +++ b/app/src/main/java/tech/oom/idealrecorderdemo/widget/WaveView.java @@ -0,0 +1,213 @@ +package tech.oom.idealrecorderdemo.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + + +import java.util.ArrayList; + +import tech.oom.idealrecorderdemo.R; + +/** + * Created by issuser on 2017/6/22 0022. + */ + +public class WaveView extends View { + private ArrayList datas = new ArrayList<>(); + private short max = 300; + private float mWidth; + private float mHeight; + private float space = 1f; + private Paint mWavePaint; + private Paint baseLinePaint; + private int mWaveColor = Color.BLACK; + private int mBaseLineColor = Color.BLACK; + private float waveStrokeWidth = 1f; + private int invalidateTime = 1000 / 200; + private long drawTime; + private boolean isMaxConstant = false; + + public WaveView(Context context) { + this(context, null); + } + + public WaveView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyle) { + + final TypedArray a = getContext().obtainStyledAttributes( + attrs, R.styleable.WaveView, defStyle, 0); + + + mWaveColor = a.getColor( + R.styleable.WaveView_waveColor, + mWaveColor); + mBaseLineColor = a.getColor( + R.styleable.WaveView_baselineColor, + mBaseLineColor); + + waveStrokeWidth = a.getDimension( + R.styleable.WaveView_waveStokeWidth, + waveStrokeWidth); + + max = (short) a.getInt(R.styleable.WaveView_maxValue, max); + invalidateTime = a.getInt(R.styleable.WaveView_invalidateTime, invalidateTime); + + space = a.getDimension(R.styleable.WaveView_space, space); + a.recycle(); + initPainters(); + + } + + private void initPainters() { + mWavePaint = new Paint(); + mWavePaint.setColor(mWaveColor);// 画笔为color + mWavePaint.setStrokeWidth(waveStrokeWidth);// 设置画笔粗细 + mWavePaint.setAntiAlias(true); + mWavePaint.setFilterBitmap(true); + mWavePaint.setStyle(Paint.Style.FILL); + + baseLinePaint = new Paint(); + baseLinePaint.setColor(mBaseLineColor);// 画笔为color + baseLinePaint.setStrokeWidth(1f);// 设置画笔粗细 + baseLinePaint.setAntiAlias(true); + baseLinePaint.setFilterBitmap(true); + baseLinePaint.setStyle(Paint.Style.FILL); + } + + public short getMax() { + return max; + } + + public void setMax(short max) { + this.max = max; + } + + public float getSpace() { + return space; + } + + public void setSpace(float space) { + this.space = space; + } + + public int getmWaveColor() { + return mWaveColor; + } + + public void setmWaveColor(int mWaveColor) { + this.mWaveColor = mWaveColor; + invalidateNow(); + } + + public int getmBaseLineColor() { + return mBaseLineColor; + } + + public void setmBaseLineColor(int mBaseLineColor) { + this.mBaseLineColor = mBaseLineColor; + invalidateNow(); + } + + public float getWaveStrokeWidth() { + return waveStrokeWidth; + } + + public void setWaveStrokeWidth(float waveStrokeWidth) { + this.waveStrokeWidth = waveStrokeWidth; + invalidateNow(); + } + + public int getInvalidateTime() { + return invalidateTime; + } + + public void setInvalidateTime(int invalidateTime) { + this.invalidateTime = invalidateTime; + } + + public boolean isMaxConstant() { + return isMaxConstant; + } + + public void setMaxConstant(boolean maxConstant) { + isMaxConstant = maxConstant; + } + + /** + * 如果改变相应配置 需要刷新相应的paint设置 + */ + public void invalidateNow() { + initPainters(); + invalidate(); + } + + public void addData(short data) { + + if (data < 0) { + data = (short) -data; + } + if (data > max && !isMaxConstant) { + max = data; + } + if (datas.size() > mWidth / space) { + synchronized (this) { + datas.remove(0); + datas.add(data); + } + } else { + datas.add(data); + } + if (System.currentTimeMillis() - drawTime > invalidateTime) { + invalidate(); + drawTime = System.currentTimeMillis(); + } + + } + + public void clear() { + datas.clear(); + invalidateNow(); + } + + + @Override + protected void onDraw(Canvas canvas) { + canvas.translate(0, mHeight / 2); + drawBaseLine(canvas); + drawWave(canvas); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + mHeight = h; + } + + private void drawWave(Canvas mCanvas) { + for (int i = 0; i < datas.size(); i++) { + float x = (i) * space; + float y = (float) datas.get(i) / max * mHeight / 2; + mCanvas.drawLine(x, -y, x, y, mWavePaint); + } + + } + + private void drawBaseLine(Canvas mCanvas) { + mCanvas.drawLine(0, 0, mWidth, 0, baseLinePaint); + } +} diff --git a/app/src/main/res/drawable/recorder_btn.xml b/app/src/main/res/drawable/recorder_btn.xml new file mode 100644 index 0000000..5c89fdd --- /dev/null +++ b/app/src/main/res/drawable/recorder_btn.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e7e7d3f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,47 @@ + + + + + + + + + +