├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── audio │ └── util │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml ├── debug │ └── AndroidManifest.xml ├── java │ └── com │ │ └── audio │ │ └── util │ │ ├── Audio.java │ │ ├── AudioEditUtil.java │ │ ├── AudioEncodeUtil.java │ │ ├── AudioMsg.java │ │ ├── AudioUtilHelper.java │ │ ├── ByteUtil.java │ │ ├── ComposeInfo.java │ │ ├── ConvertUtil.java │ │ ├── DecodeActivity.kt │ │ ├── DecodeEngine.java │ │ ├── DecodeUtil.java │ │ ├── FileFunction.java │ │ ├── LogUtil.java │ │ ├── MainActivity.kt │ │ ├── MixAudioUtil.java │ │ ├── MultiAudioMixer.java │ │ ├── Record2Activity.kt │ │ ├── RecordActivity.kt │ │ ├── WavMergeUtil.java │ │ ├── callback │ │ └── DecodeOperateInterface.java │ │ ├── cut │ │ └── AudioCutUtil.java │ │ ├── encode │ │ └── CommonFunction.java │ │ ├── record │ │ └── AudioRecordUtil.kt │ │ ├── ssrc │ │ ├── I0Bessel.java │ │ ├── SSRC.java │ │ └── SplitRadixFft.java │ │ └── util │ │ ├── Constant.java │ │ └── FileUtils.java └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_decode.xml │ ├── activity_main.xml │ ├── activity_record.xml │ └── activity_record2.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-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml └── test └── java └── com └── audio └── util └── ExampleUnitTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AudioUtil 2 | AudioUtil module 3 | 4 | 5 | 模块简介: 6 | ================================================ 7 | 8 | 主要实现了音频的录制,获取媒体文件时长(可以是本地文件也可以是远端网络文件),mp3格式转换pcm,pcm转换为wav(至于wav转换成mp3请参考另一模块( https://github.com/fanyuan/mp3Convert ); 9 | 10 | 使用场景: 11 | ================================================ 12 | 13 | 1、从录制到编辑合并一条龙: 14 | 用录制功能录好pcm后,采用转码功能把pcm转换成wav可播放格式,可以把wav文件进行剪切、拼接、混音合成等操作后,生成最终的wav文件 15 | 2、各个功能拆开单独使用 16 | 17 | 使用指导:为了实现对于调用者友好使用,本模块使用用AudioUtilHelper进行了各功能模块的封装,AudioUtilHelper作为统一入口;以使使用者不用关心实现细节,迷失在代码丛林; 18 | 19 | 功能介绍: 20 | ================================================ 21 | 1、录音功能: 22 | ================================================ 23 | 支持断点续录,调用startRecord开始录制,比如录制过程中有事中断去做其他事情,可以选择暂停(pause)或停止(finishRecord),然后想接着录时就可以接着录,只要目标文件路径一样就好继续录制; 24 | 比如录唱歌曲时,中间有事打断,可以在处理好事情后,在接着录制; 25 | 其中暂停(pause)方法不会释放资源,停止(finishRecord)方法会释放资源; 26 | 如果想要删除掉已录制的文件可以调用giveUpRecord方法放弃当前的录制,并删除相关文件; 27 | startRecord方法是重录功能,把之前的录制文件删除重新开始录制; 28 | 29 | 2、获取相关音频文件时长: 30 | ================================================ 31 | 如果想知道一个音频文件的播放时长可以通过getAudioDuration方法获取,支持本地文件和网络远程文件; 32 | 有用过exoplayer和mediaplayer都知道这两个播放器对于音频文件的时长是不精确的,有误差,可以用此方法解决; 33 | 34 | 3、把mp3文件解码转换成mav格式: 35 | ================================================ 36 | mp3ToWav方法可以把mp3文件转换成wav格式; 37 | 使用场景:如果想自己录制一首歌曲后,想加入一个喜欢的背景乐,就可以从其他地方得到伴奏的mp3然后转成wav,用本模块的合并功能进行混音合成(合成方法mixAudio下面会讲); 38 | 39 | 4、音频混音合并功能: 40 | ================================================ 41 | 用来进行混音合成,调用mixAudio方法来进行;可以把两个wav文件全成为一个,并且通过权重可以调节音量比例,比如想要录制的声音70%,背景音30%; 42 | 43 | 5、音频文件拼接: 44 | ================================================ 45 | 用来拼接多个音频文件,调用mergeWav方法来完成多个音频文件的拼接; 46 | 比如想把多首自己喜欢的歌曲拼接到一起,形成一大的个文件进行各个歌曲的连续播放; 47 | 48 | 6、音频文件的剪辑: 49 | ================================================ 50 | 用来提取相关音频文件的片段,调用cutAudio方法通过传入开始时间和结束时间来提取时间区间的音频部分,比如想提取某首歌曲的某几句歌; 51 | 52 | 至于把wav文件转换成mp3可以参考另一模块(https://github.com/fanyuan/mp3Convert) 53 | 54 | 55 | 使用示例: 56 | ================================================ 57 | 58 | 录制功能 59 | ================================================ 60 | 61 | java 62 | ================================================ 63 | String path = AudioUtilHelper.getRecordLocalTempFilePath(this, "一个小测试"); 64 | AudioUtilHelper.startRecord(this, path, object : RecordCallback { 65 | override fun onRecordError(errorMsg: String) { 66 | ThreadHelper.runOnUiThreadDelay({ 67 | DialogAlterHelper.showSingleButtonDialog(this@RecordActivity, errorMsg, { toReRecord() }) 68 | }, 300) 69 | 70 | 71 | Apputils.log(applicationContext, "RecordActivity onRecordError $errorMsg") 72 | } 73 | 74 | override fun onRecordStatusChanged(isInRecording: Boolean) { 75 | Apputils.log(applicationContext, "RecordActivity onRecordStatusChanged $isInRecording") 76 | } 77 | 78 | }) 79 | 80 | kotlin 81 | ================================================ 82 | fun start(v: View){ 83 | val path = externalCacheDir?.absolutePath.toString() + File.separator + "hello/test3.wav" 84 | AudioUtilHelper.startRecord(this,path,object :RecordCallback{ 85 | override fun onRecordError(errorMsg: String) { 86 | Log.d("ddebug","Record2Activity onRecordError $errorMsg") 87 | } 88 | 89 | override fun onRecordStatusChanged(isInRecording: Boolean) { 90 | Log.d("ddebug","Record2Activity onRecordStatusChanged $isInRecording") 91 | } 92 | 93 | }) 94 | } 95 | fun pause(v: View){ 96 | AudioUtilHelper.pause() 97 | } 98 | fun finish(v: View){ 99 | AudioUtilHelper.finishRecord(null) 100 | } 101 | 102 | ----------------------------- 103 | 104 | mp3转wav 105 | ================================================ 106 | 107 | 108 | val mp3Path = "/sdcard/test.mp3" 109 | val wavPath = "/sdcard/target.wav" 110 | 111 | AudioUtilHelper.mp3ToWav(mp3Path, wavPath, object : DecodeUtil.DecodeOperateInterface { 112 | override fun onLoadedAudioInfo(audio: String?) { 113 | Apputils.log(applicationContext, "onLoadedAudioInfo -- audio = $audio") 114 | } 115 | 116 | override fun onLoadedError(audio: String?) { 117 | Apputils.log(applicationContext, "onLoadedError -- audio = $audio") 118 | hasError = true 119 | if (audio != null) { 120 | errorMs = audio 121 | } 122 | } 123 | }) 124 | 125 | -------------------------------- 126 | 127 | 音频合成功能 128 | ================================================ 129 | Thread { 130 | var path1 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 131 | var path2 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘convert1610201261595.wav" 132 | var pathOut = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘_姑娘我爱你_mix.wav" 133 | //MixAudioUtil.mixAudio(path1, path2, pathOut, 0.3f, 0.5f); 134 | AudioUtilHelper.mixAudio(path1, path2, pathOut, 0.3f, 0.5f) 135 | Log.d("ddebug","---run---") 136 | }.start() 137 | 138 | -------------------------------- 139 | 140 | 音频拼接功能 141 | ================================================ 142 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/春晓.wav"//"temp/遇上你是我的缘convert1610201261595.wav" 143 | var path1 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 144 | var pathOut = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/春晓merge.wav"//"temp/遇上你是我的缘merge.wav" 145 | val fileIn = File(path) 146 | val fileOut = File(pathOut); 147 | val list = listOf(fileIn, fileIn)//File(path1) 148 | 149 | Thread(){ 150 | AudioUtilHelper.mergeWav(list,fileOut) 151 | }.start(); 152 | 153 | -------------------------------- 154 | 155 | 音频提取功能 156 | ================================================ 157 | Thread(){ 158 | log("---cutAudio---") 159 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/春晓.wav"//"temp/姑娘我爱你convert1610210798289.wav" 160 | var path2 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/春晓cut1.wav"//"temp/姑娘我爱你cut1.wav" 161 | //AudioCutUtil.cutAudio(path,path2,20f,30f) 162 | AudioUtilHelper.cutAudio(path,path2,5f,15f); 163 | }.start(); 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | if(isAudioUtilLibrary.toBoolean()){ 2 | apply plugin: 'com.android.library' 3 | }else { 4 | apply plugin: 'com.android.application' 5 | } 6 | //plugins { 7 | // id 'kotlin-android' 8 | //} 9 | apply plugin: 'kotlin-android' 10 | apply plugin: 'kotlin-android-extensions' 11 | android { 12 | compileSdkVersion 30 13 | buildToolsVersion "30.0.3" 14 | 15 | defaultConfig { 16 | 17 | if(!isAudioUtilLibrary.toBoolean()){ 18 | applicationId "com.audio.util" 19 | } 20 | 21 | minSdkVersion 18 22 | targetSdkVersion 30 23 | versionCode 1 24 | versionName "1.0" 25 | 26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 27 | } 28 | dataBinding { 29 | enabled = true 30 | } 31 | sourceSets{ 32 | main{ 33 | if(isAudioUtilLibrary.toBoolean()){ 34 | manifest.srcFile 'src/main/debug/AndroidManifest.xml' 35 | }else { 36 | manifest.srcFile 'src/main/AndroidManifest.xml' 37 | } 38 | 39 | } 40 | } 41 | buildTypes { 42 | release { 43 | minifyEnabled false 44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 45 | jniDebuggable true 46 | //signingConfig signingConfigs.release 47 | } 48 | debug { 49 | minifyEnabled false 50 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 51 | jniDebuggable true 52 | debuggable true 53 | //signingConfig signingConfigs.release 54 | } 55 | } 56 | compileOptions { 57 | sourceCompatibility JavaVersion.VERSION_1_8 58 | targetCompatibility JavaVersion.VERSION_1_8 59 | } 60 | kotlinOptions { 61 | jvmTarget = '1.8' 62 | } 63 | } 64 | 65 | dependencies { 66 | 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 68 | implementation 'androidx.core:core-ktx:1.2.0' 69 | implementation 'androidx.appcompat:appcompat:1.2.0' 70 | implementation 'com.google.android.material:material:1.2.1' 71 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 72 | testImplementation 'junit:junit:4.+' 73 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 74 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 75 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /src/androidTest/java/com/audio/util/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.audio.util", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/Audio.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.MediaExtractor; 5 | import android.media.MediaFormat; 6 | import android.os.Build; 7 | 8 | import androidx.annotation.RequiresApi; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | 13 | /** 14 | * 音频信息 15 | */ 16 | public class Audio { 17 | private String path; 18 | private String name; 19 | private float volume = 1f; 20 | private int channel = 2; 21 | private int sampleRate = 44100; 22 | private int bitNum = 16; 23 | private int timeMillis; 24 | 25 | public String getPath() { 26 | return path; 27 | } 28 | 29 | public void setPath(String path) { 30 | this.path = path; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public float getVolume() { 42 | return volume; 43 | } 44 | 45 | public void setVolume(float volume) { 46 | this.volume = volume; 47 | } 48 | 49 | public int getChannel() { 50 | return channel; 51 | } 52 | 53 | public void setChannel(int channel) { 54 | this.channel = channel; 55 | } 56 | 57 | public int getSampleRate() { 58 | return sampleRate; 59 | } 60 | 61 | public void setSampleRate(int sampleRate) { 62 | this.sampleRate = sampleRate; 63 | } 64 | 65 | public int getBitNum() { 66 | return bitNum; 67 | } 68 | 69 | public void setBitNum(int bitNum) { 70 | this.bitNum = bitNum; 71 | } 72 | 73 | public int getTimeMillis() { 74 | return timeMillis; 75 | } 76 | 77 | public void setTimeMillis(int timeMillis) { 78 | this.timeMillis = timeMillis; 79 | } 80 | 81 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public static Audio createAudioFromFile(File inputFile) throws Exception { 82 | MediaExtractor extractor = new MediaExtractor(); 83 | MediaFormat format = null; 84 | int i; 85 | 86 | try { 87 | extractor.setDataSource(inputFile.getPath()); 88 | }catch (Exception ex){ 89 | ex.printStackTrace(); 90 | extractor.setDataSource(new FileInputStream(inputFile).getFD()); 91 | } 92 | 93 | int numTracks = extractor.getTrackCount(); 94 | for (i = 0; i < numTracks; i++) { 95 | format = extractor.getTrackFormat(i); 96 | if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) { 97 | extractor.selectTrack(i); 98 | break; 99 | } 100 | } 101 | if (i == numTracks) { 102 | throw new Exception("No audio track found in " + inputFile); 103 | } 104 | 105 | Audio audio = new Audio(); 106 | audio.name = inputFile.getName(); 107 | audio.path = inputFile.getAbsolutePath(); 108 | audio.sampleRate = format.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? format.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100; 109 | audio.channel = format.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1; 110 | audio.timeMillis = (int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000.f)); 111 | 112 | //根据pcmEncoding编码格式,得到采样精度,MediaFormat.KEY_PCM_ENCODING这个值不一定有 113 | int pcmEncoding = format.containsKey(MediaFormat.KEY_PCM_ENCODING) ? format.getInteger(MediaFormat.KEY_PCM_ENCODING) : AudioFormat.ENCODING_PCM_16BIT; 114 | switch (pcmEncoding){ 115 | case AudioFormat.ENCODING_PCM_FLOAT: 116 | audio.bitNum = 32; 117 | break; 118 | case AudioFormat.ENCODING_PCM_8BIT: 119 | audio.bitNum = 8; 120 | break; 121 | case AudioFormat.ENCODING_PCM_16BIT: 122 | default: 123 | audio.bitNum = 16; 124 | break; 125 | } 126 | 127 | extractor.release(); 128 | 129 | return audio; 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/AudioEditUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.audio.util.util.FileUtils; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | 11 | /** 12 | * 音频合成 13 | */ 14 | 15 | public class AudioEditUtil { 16 | 17 | //wave头文件大小 18 | private static final int WAVE_HEAD_SIZE = 44; 19 | 20 | 21 | /** 22 | * 裁剪音频 23 | * @param audio 音频信息 24 | * @param cutStartTime 裁剪开始时间 25 | * @param cutEndTime 裁剪结束时间 26 | */ 27 | public static void cutAudio(Audio audio, float cutStartTime, float cutEndTime){ 28 | if(cutStartTime == 0 && cutEndTime == audio.getTimeMillis() / 1000f){ 29 | return; 30 | } 31 | if(cutStartTime >= cutEndTime){ 32 | return; 33 | } 34 | 35 | String srcWavePath = audio.getPath(); 36 | int sampleRate = audio.getSampleRate(); 37 | int channels = audio.getChannel(); 38 | int bitNum = audio.getBitNum(); 39 | RandomAccessFile srcFis = null; 40 | RandomAccessFile newFos = null; 41 | String tempOutPath = srcWavePath + ".temp"; 42 | try { 43 | 44 | //创建输入流 45 | srcFis = new RandomAccessFile(srcWavePath, "rw"); 46 | newFos = new RandomAccessFile(tempOutPath, "rw"); 47 | 48 | //源文件开始读取位置,结束读取文件,读取数据的大小 49 | final int cutStartPos = getPositionFromWave(cutStartTime, sampleRate, channels, bitNum); 50 | final int cutEndPos = getPositionFromWave(cutEndTime, sampleRate, channels, bitNum); 51 | final int contentSize = cutEndPos - cutStartPos; 52 | 53 | //复制wav head 字节数据 54 | byte[] headerData = AudioEncodeUtil.getWaveHeader(contentSize, sampleRate, channels, bitNum); 55 | copyHeadData(headerData, newFos); 56 | 57 | //移动到文件开始读取处 58 | srcFis.seek(WAVE_HEAD_SIZE + cutStartPos); 59 | 60 | //复制裁剪的音频数据 61 | copyData(srcFis, newFos, contentSize); 62 | 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | 66 | return; 67 | 68 | }finally { 69 | //关闭输入流 70 | if(srcFis != null){ 71 | try { 72 | srcFis.close(); 73 | } catch (IOException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | if(newFos != null){ 78 | try { 79 | newFos.close(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | 86 | // 删除源文件, 87 | new File(srcWavePath).delete(); 88 | //重命名为源文件 89 | FileUtils.renameFile(new File(tempOutPath), audio.getPath()); 90 | 91 | } 92 | 93 | /** 94 | * 插入音频,针对声道数,采样率,采样位数都一样的wav音频 95 | * @param srcAudio 源音频路径 96 | * @param coverAudio 覆盖音频路径 97 | * @param outAudio 98 | * @param srcStartTime 源音频起始时间 99 | */ 100 | public static void insertAudioWithSame(Audio srcAudio, Audio coverAudio, Audio outAudio, float srcStartTime){ 101 | 102 | String srcWavePath = srcAudio.getPath(); 103 | String coverWavePath = coverAudio.getPath(); 104 | int sampleRate = srcAudio.getSampleRate(); 105 | int channels = srcAudio.getChannel(); 106 | int bitNum = srcAudio.getBitNum(); 107 | RandomAccessFile srcFis = null; 108 | RandomAccessFile coverFis = null; 109 | RandomAccessFile newFos = null; 110 | String tempOutPcmPath = srcWavePath + ".tempPcm"; 111 | try { 112 | 113 | //创建输入流 114 | srcFis = new RandomAccessFile(srcWavePath, "rw"); 115 | coverFis = new RandomAccessFile(coverWavePath, "rw"); 116 | newFos = new RandomAccessFile(tempOutPcmPath, "rw"); 117 | 118 | final int srcStartPos = getPositionFromWave(srcStartTime, sampleRate, channels, bitNum); 119 | final int coverStartPos = 0; 120 | final int coverEndPos = (int) coverFis.length() - WAVE_HEAD_SIZE; 121 | 122 | //复制源音频srcStartTime时间之前的数据 123 | //跳过头文件数据 124 | srcFis.seek(WAVE_HEAD_SIZE); 125 | 126 | copyData(srcFis, newFos, srcStartPos); 127 | 128 | //复制覆盖音频指定时间段的数据 129 | //跳过指定位置数据 130 | coverFis.seek(WAVE_HEAD_SIZE + coverStartPos); 131 | 132 | int copyCoverSize = coverEndPos - coverStartPos; 133 | float volume = coverAudio.getVolume(); 134 | copyData(coverFis, newFos, copyCoverSize, volume); 135 | 136 | //复制srcStartTime时间后的源文件数据 137 | final long srcFileSize = new File(srcWavePath).length() - WAVE_HEAD_SIZE; 138 | int remainSize = (int) (srcFileSize - srcStartPos); 139 | if(remainSize > 0){ 140 | 141 | coverFis.seek(WAVE_HEAD_SIZE + coverStartPos); 142 | copyData(srcFis, newFos, remainSize); 143 | 144 | } 145 | 146 | } catch (Exception e) { 147 | e.printStackTrace(); 148 | 149 | return; 150 | 151 | }finally { 152 | //关闭输入流 153 | if(srcFis != null){ 154 | try { 155 | srcFis.close(); 156 | } catch (IOException e) { 157 | e.printStackTrace(); 158 | } 159 | } 160 | if(coverFis != null){ 161 | try { 162 | coverFis.close(); 163 | } catch (IOException e) { 164 | e.printStackTrace(); 165 | } 166 | } 167 | if(newFos != null){ 168 | try { 169 | newFos.close(); 170 | } catch (IOException e) { 171 | e.printStackTrace(); 172 | } 173 | } 174 | } 175 | 176 | // 删除源文件, 177 | //new File(srcWavePath).delete(); 178 | // 转换临时文件为源文件 179 | AudioEncodeUtil.convertPcm2Wav(tempOutPcmPath, outAudio.getPath(), sampleRate, channels, bitNum); 180 | //删除临时文件 181 | new File(tempOutPcmPath).delete(); 182 | } 183 | 184 | /** 185 | * 混合音频,针对声道数,采样率,采样位数都一样的wav音频 186 | * 187 | * @param srcAudio 源音频路径 188 | * @param coverAudio 覆盖音频路径 189 | * @param startTime 源音频起始时间 190 | * @param progress1 源音频音频强度 191 | * @param progress2 附加音频音频强度 192 | */ 193 | public static void mixAudioWithSame(Audio srcAudio, Audio coverAudio, Audio outAudio, float startTime, float progress1, float progress2) { 194 | 195 | String srcWavePath = srcAudio.getPath(); 196 | String coverWavePath = coverAudio.getPath(); 197 | int sampleRate = srcAudio.getSampleRate(); 198 | int channels = srcAudio.getChannel(); 199 | int bitNum = srcAudio.getBitNum(); 200 | RandomAccessFile srcFis = null; 201 | RandomAccessFile coverFis = null; 202 | RandomAccessFile newFos = null; 203 | String tempOutPcmPath = outAudio.getPath() + ".tempPcm"; 204 | try { 205 | 206 | //创建输入流 207 | srcFis = new RandomAccessFile(srcWavePath, "rw"); 208 | coverFis = new RandomAccessFile(coverWavePath, "rw"); 209 | newFos = new RandomAccessFile(tempOutPcmPath, "rw"); 210 | 211 | final int srcStartPos = getPositionFromWave(startTime, sampleRate, channels, bitNum); 212 | final int coverStartPos = 0; 213 | final int coverEndPos = (int) coverFis.length() - WAVE_HEAD_SIZE; 214 | 215 | //复制源音频srcStartTime时间之前的数据 216 | //跳过头文件数据 217 | srcFis.seek(WAVE_HEAD_SIZE); 218 | 219 | copyData(srcFis, newFos, srcStartPos); 220 | 221 | //跳过指定位置数据 222 | coverFis.seek(WAVE_HEAD_SIZE); 223 | //混合音频指定时间段的数据 224 | int copyCoverSize = coverEndPos - coverStartPos; 225 | mixData(srcFis, coverFis, newFos, copyCoverSize, progress1, progress2); 226 | 227 | //判断源音频后面是否还有音频数据,如果有则复制 228 | final long srcFileSize = srcFis.getChannel().size(); 229 | int remainSize = (int) (srcFileSize - (srcStartPos + copyCoverSize)); 230 | if (remainSize > 0) { 231 | 232 | //跳过指定位置数据 233 | srcFis.seek(WAVE_HEAD_SIZE + srcStartPos + copyCoverSize); 234 | copyData(srcFis, newFos, remainSize); 235 | } 236 | } catch (Exception e) { 237 | e.printStackTrace(); 238 | 239 | return; 240 | } finally { 241 | //关闭输入流 242 | if (srcFis != null) { 243 | try { 244 | srcFis.close(); 245 | } catch (IOException e) { 246 | e.printStackTrace(); 247 | } 248 | } 249 | if (coverFis != null) { 250 | try { 251 | coverFis.close(); 252 | } catch (IOException e) { 253 | e.printStackTrace(); 254 | } 255 | } 256 | if (newFos != null) { 257 | try { 258 | newFos.close(); 259 | } catch (IOException e) { 260 | e.printStackTrace(); 261 | } 262 | } 263 | } 264 | 265 | // 删除源文件, 266 | //new File(srcWavePath).delete(); 267 | // 转换临时文件为源文件 268 | AudioEncodeUtil.convertPcm2Wav(tempOutPcmPath, outAudio.getPath(), sampleRate, channels, bitNum); 269 | //删除临时文件 270 | new File(tempOutPcmPath).delete(); 271 | } 272 | 273 | /** 274 | * 指定基准音频 275 | * 混合音频,针对声道数,采样率,采样位数都一样的wav音频 276 | * 277 | * @param srcAudio 源音频路径 278 | * @param coverAudio 覆盖音频路径 279 | * @param startTime 源音频起始时间 280 | * @param progress1 源音频音频强度 281 | * @param progress2 附加音频音频强度 282 | */ 283 | public static void mixAudioWithSameBaseSrcAudio(Audio srcAudio, Audio coverAudio, Audio outAudio, float startTime, float progress1, float progress2) { 284 | 285 | String srcWavePath = srcAudio.getPath(); 286 | String coverWavePath = coverAudio.getPath(); 287 | int sampleRate = srcAudio.getSampleRate(); 288 | int channels = srcAudio.getChannel(); 289 | int bitNum = srcAudio.getBitNum(); 290 | RandomAccessFile srcFis = null; 291 | RandomAccessFile coverFis = null; 292 | RandomAccessFile newFos = null; 293 | String tempOutPcmPath = outAudio.getPath() + ".tempPcm"; 294 | try { 295 | 296 | //创建输入流 297 | srcFis = new RandomAccessFile(srcWavePath, "rw"); 298 | coverFis = new RandomAccessFile(coverWavePath, "rw"); 299 | newFos = new RandomAccessFile(tempOutPcmPath, "rw"); 300 | 301 | final int srcStartPos = getPositionFromWave(startTime, sampleRate, channels, bitNum); 302 | final int srcEndPos = (int) (srcFis.length() - WAVE_HEAD_SIZE); 303 | final int coverStartPos = 0; 304 | final int coverEndPos = (int) coverFis.length() - WAVE_HEAD_SIZE; 305 | 306 | //复制源音频srcStartTime时间之前的数据 307 | //跳过头文件数据 308 | srcFis.seek(WAVE_HEAD_SIZE); 309 | //跳过指定位置数据 310 | coverFis.seek(WAVE_HEAD_SIZE); 311 | 312 | //混合音频指定时间段的数据 313 | int copyCoverSize = coverEndPos - coverStartPos; 314 | int copySrcSize = srcEndPos - srcStartPos; 315 | int times = 0; 316 | if(copySrcSize > copyCoverSize){ 317 | times = copySrcSize/copyCoverSize; 318 | } 319 | int size = (int) (srcFis.getChannel().size() - WAVE_HEAD_SIZE); 320 | Log.d("ddebug","mix audio times = " + times + "---size = " + size + "---srcStartPos=" + srcStartPos); 321 | copyData(srcFis, newFos, srcStartPos);//copyData(srcFis, newFos, copyCoverSize/2);// 322 | mixData(srcFis, coverFis, newFos, copyCoverSize, progress1, progress2); 323 | 324 | //判断源音频后面是否还有音频数据,如果有则复制 325 | // final long srcFileSize = srcFis.getChannel().size(); 326 | // int remainSize = (int) (srcFileSize - (srcStartPos + copyCoverSize)); 327 | // if (remainSize > 0) { 328 | // 329 | // //跳过指定位置数据 330 | // srcFis.seek(WAVE_HEAD_SIZE + srcStartPos + copyCoverSize); 331 | // copyData(srcFis, newFos, remainSize); 332 | // } 333 | } catch (Exception e) { 334 | e.printStackTrace(); 335 | 336 | return; 337 | } finally { 338 | //关闭输入流 339 | if (srcFis != null) { 340 | try { 341 | srcFis.close(); 342 | } catch (IOException e) { 343 | e.printStackTrace(); 344 | } 345 | } 346 | if (coverFis != null) { 347 | try { 348 | coverFis.close(); 349 | } catch (IOException e) { 350 | e.printStackTrace(); 351 | } 352 | } 353 | if (newFos != null) { 354 | try { 355 | newFos.close(); 356 | } catch (IOException e) { 357 | e.printStackTrace(); 358 | } 359 | } 360 | } 361 | 362 | // 删除源文件, 363 | //new File(srcWavePath).delete(); 364 | // 转换临时文件为源文件 365 | AudioEncodeUtil.convertPcm2Wav(tempOutPcmPath, outAudio.getPath(), sampleRate, channels, bitNum); 366 | //删除临时文件 367 | new File(tempOutPcmPath).delete(); 368 | } 369 | /** 370 | * 获取wave文件某个时间对应的数据位置 371 | * @param time 时间 372 | * @param sampleRate 采样率 373 | * @param channels 声道数 374 | * @param bitNum 采样位数 375 | * @return 376 | */ 377 | private static int getPositionFromWave(float time, int sampleRate, int channels, int bitNum) { 378 | int byteNum = bitNum / 8; 379 | int position = (int) (time * sampleRate * channels * byteNum); 380 | 381 | position = position / (byteNum * channels) * (byteNum * channels); 382 | 383 | return position; 384 | } 385 | 386 | /** 387 | * 复制wav header 数据 388 | * 389 | * @param headerData wav header 数据 390 | * @param fos 目标输出流 391 | */ 392 | private static void copyHeadData(byte[] headerData, RandomAccessFile fos) { 393 | try { 394 | fos.seek(0); 395 | fos.write(headerData); 396 | } catch (Exception ex) { 397 | ex.printStackTrace(); 398 | } 399 | } 400 | 401 | /** 402 | * 复制数据 403 | * 404 | * @param fis 源输入流 405 | * @param fos 目标输出流 406 | * @param cooySize 复制大小 407 | */ 408 | private static void copyData(RandomAccessFile fis, RandomAccessFile fos, final int cooySize) { 409 | 410 | byte[] buffer = new byte[2048]; 411 | int length; 412 | int totalReadLength = 0; 413 | 414 | try { 415 | 416 | while ((length = fis.read(buffer)) != -1) { 417 | 418 | fos.write(buffer, 0, length); 419 | 420 | totalReadLength += length; 421 | 422 | int remainSize = cooySize - totalReadLength; 423 | if (remainSize <= 0) { 424 | //读取指定位置完成 425 | break; 426 | } else if (remainSize < buffer.length) { 427 | //离指定位置的大小小于buffer的大小,换remainSize的buffer 428 | buffer = new byte[remainSize]; 429 | } 430 | } 431 | } catch (Exception ex) { 432 | ex.printStackTrace(); 433 | } 434 | } 435 | 436 | /** 437 | * 复制数据 438 | * @param fis 源输入流 439 | * @param fos 目标输出流 440 | * @param cooySize 复制大小 441 | * @param volumeValue 音量调节大小 442 | */ 443 | private static void copyData(RandomAccessFile fis, RandomAccessFile fos, final int cooySize, final float volumeValue){ 444 | 445 | byte[] buffer = new byte[2048]; 446 | int length; 447 | int totalReadLength = 0; 448 | 449 | try { 450 | 451 | while((length = fis.read(buffer)) != -1){ 452 | 453 | fos.write(changeDataWithVolume(buffer, volumeValue), 0, length); 454 | 455 | totalReadLength += length; 456 | 457 | int remainSize = cooySize - totalReadLength; 458 | if(remainSize <= 0){ 459 | //读取指定位置完成 460 | break; 461 | }else if(remainSize < buffer.length){ 462 | //离指定位置的大小小于buffer的大小,换remainSize的buffer 463 | buffer = new byte[remainSize]; 464 | } 465 | 466 | } 467 | 468 | }catch (Exception ex){ 469 | ex.printStackTrace(); 470 | } 471 | 472 | } 473 | 474 | /** 475 | * 合成音频 476 | */ 477 | private static void mixData(RandomAccessFile srcFis, RandomAccessFile coverFis, 478 | RandomAccessFile fos, final int copySize, float volumeAudio1, float volumeAudio2) { 479 | 480 | MultiAudioMixer mix = MultiAudioMixer.createDefaultAudioMixer(); 481 | 482 | byte[] srcBuffer = new byte[2048]; 483 | byte[] coverBuffer = new byte[2048]; 484 | int length; 485 | int totalReadLength = 0; 486 | 487 | try { 488 | 489 | while ((length = coverFis.read(coverBuffer)) != -1) { 490 | 491 | srcFis.read(srcBuffer); 492 | srcBuffer = changeDataWithVolume(srcBuffer, volumeAudio1); 493 | coverBuffer = changeDataWithVolume(coverBuffer, volumeAudio2); 494 | 495 | byte[] mixData = mix.mixRawAudioBytes(new byte[][] { srcBuffer, coverBuffer }); 496 | fos.write(mixData); 497 | 498 | totalReadLength += length; 499 | 500 | int remainSize = copySize - totalReadLength; 501 | if (remainSize <= 0) { 502 | //读取指定位置完成 503 | break; 504 | } else if (remainSize < coverBuffer.length) { 505 | //离指定位置的大小小于buffer的大小,换remainSize的buffer 506 | coverBuffer = new byte[remainSize]; 507 | srcBuffer = new byte[remainSize]; 508 | } 509 | } 510 | } catch (Exception ex) { 511 | ex.printStackTrace(); 512 | } 513 | } 514 | 515 | /** 516 | * 改变音量 517 | */ 518 | private static byte[] changeDataWithVolume(byte[] buffer, float volumeValue) { 519 | 520 | for (int i = 0; i < buffer.length; i = i + 2) { 521 | int value = ByteUtil.byte2Short(buffer[i + 1], buffer[i]); 522 | int tempValue = value; 523 | value *= volumeValue; 524 | value = value > 0x7fff ? 0x7fff : value; 525 | value = value < -0x8000 ? -0x8000 : value; 526 | 527 | short newValue = (short) value; 528 | 529 | byte[] array = ByteUtil.short2Byte(newValue); 530 | buffer[i + 1] = array[0]; 531 | buffer[i] = array[1]; 532 | } 533 | 534 | return buffer; 535 | } 536 | 537 | } 538 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/AudioEncodeUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import com.audio.util.util.Constant; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | 9 | /** 10 | * 音频格式转换 11 | */ 12 | public class AudioEncodeUtil { 13 | 14 | /** 15 | * wav转pcm 16 | */ 17 | public static void convertWav2Pcm(String inWaveFilePath, String outPcmFilePath) { 18 | 19 | FileInputStream in = null; 20 | FileOutputStream out = null; 21 | byte[] data = new byte[1024]; 22 | 23 | try { 24 | 25 | in = new FileInputStream(inWaveFilePath); 26 | out = new FileOutputStream(outPcmFilePath); 27 | 28 | byte[] header = new byte[44]; 29 | in.read(header); 30 | 31 | int length = 0; 32 | while ((length = in.read(data)) > 0) { 33 | out.write(data, 0, length); 34 | } 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } finally { 38 | if (in != null) { 39 | try { 40 | in.close(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | if (out != null) { 46 | try { 47 | out.close(); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * pcm转wav 57 | */ 58 | public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath) { 59 | 60 | convertPcm2Wav(inPcmFilePath, outWavFilePath, Constant.ExportSampleRate, 61 | Constant.ExportChannelNumber, 16); 62 | } 63 | 64 | 65 | /** 66 | * PCM文件转WAV文件 67 | * @param inPcmFilePath 输入PCM文件路径 68 | * @param outWavFilePath 输出WAV文件路径 69 | * @param sampleRate 采样率,例如44100 70 | * @param channels 声道数 单声道:1或双声道:2 71 | * @param bitNum 采样位数,8或16 72 | */ 73 | public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate, 74 | int channels, int bitNum) { 75 | 76 | FileInputStream in = null; 77 | FileOutputStream out = null; 78 | byte[] data = new byte[1024]; 79 | 80 | try { 81 | 82 | in = new FileInputStream(inPcmFilePath); 83 | out = new FileOutputStream(outWavFilePath); 84 | 85 | //PCM文件大小 86 | long totalAudioLen = in.getChannel().size(); 87 | 88 | writeWaveFileHeader(out, totalAudioLen, sampleRate, channels, bitNum); 89 | 90 | int length = 0; 91 | while ((length = in.read(data)) > 0) { 92 | out.write(data, 0, length); 93 | } 94 | } catch (Exception e) { 95 | e.printStackTrace(); 96 | } finally { 97 | if (in != null) { 98 | try { 99 | in.close(); 100 | } catch (IOException e) { 101 | e.printStackTrace(); 102 | } 103 | } 104 | if (out != null) { 105 | try { 106 | out.close(); 107 | } catch (IOException e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * 输出WAV文件 116 | * @param out WAV输出文件流 117 | * @param totalAudioLen 整个音频PCM数据大小 118 | * @param sampleRate 采样率 119 | * @param channels 声道数 120 | * @param bitNum 采样位数 121 | * @throws IOException 122 | */ 123 | private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, 124 | int sampleRate, int channels, int bitNum) throws IOException { 125 | byte[] header = getWaveHeader(totalAudioLen, sampleRate, channels, bitNum); 126 | out.write(header, 0, 44); 127 | } 128 | 129 | /** 130 | * 获取Wav header 字节数据 131 | * @param totalAudioLen 整个音频PCM数据大小 132 | * @param sampleRate 采样率 133 | * @param channels 声道数 134 | * @param bitNum 采样位数 135 | * @throws IOException 136 | */ 137 | public static byte[] getWaveHeader(long totalAudioLen, int sampleRate, int channels, int bitNum) throws IOException { 138 | 139 | //总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小 140 | long totalDataLen = totalAudioLen + 36; 141 | //采样字节byte率 142 | long byteRate = sampleRate * channels * bitNum / 8; 143 | 144 | byte[] header = new byte[44]; 145 | header[0] = 'R'; // RIFF 146 | header[1] = 'I'; 147 | header[2] = 'F'; 148 | header[3] = 'F'; 149 | header[4] = (byte) (totalDataLen & 0xff);//数据大小 150 | header[5] = (byte) ((totalDataLen >> 8) & 0xff); 151 | header[6] = (byte) ((totalDataLen >> 16) & 0xff); 152 | header[7] = (byte) ((totalDataLen >> 24) & 0xff); 153 | header[8] = 'W';//WAVE 154 | header[9] = 'A'; 155 | header[10] = 'V'; 156 | header[11] = 'E'; 157 | //FMT Chunk 158 | header[12] = 'f'; // 'fmt ' 159 | header[13] = 'm'; 160 | header[14] = 't'; 161 | header[15] = ' ';//过渡字节 162 | //数据大小 163 | header[16] = 16; // 4 bytes: size of 'fmt ' chunk 164 | header[17] = 0; 165 | header[18] = 0; 166 | header[19] = 0; 167 | //编码方式 10H为PCM编码格式 168 | header[20] = 1; // format = 1 169 | header[21] = 0; 170 | //通道数 171 | header[22] = (byte) channels; 172 | header[23] = 0; 173 | //采样率,每个通道的播放速度 174 | header[24] = (byte) (sampleRate & 0xff); 175 | header[25] = (byte) ((sampleRate >> 8) & 0xff); 176 | header[26] = (byte) ((sampleRate >> 16) & 0xff); 177 | header[27] = (byte) ((sampleRate >> 24) & 0xff); 178 | //音频数据传送速率,采样率*通道数*采样深度/8 179 | header[28] = (byte) (byteRate & 0xff); 180 | header[29] = (byte) ((byteRate >> 8) & 0xff); 181 | header[30] = (byte) ((byteRate >> 16) & 0xff); 182 | header[31] = (byte) ((byteRate >> 24) & 0xff); 183 | // 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数 184 | header[32] = (byte) (channels * 16 / 8); 185 | header[33] = 0; 186 | //每个样本的数据位数 187 | header[34] = 16; 188 | header[35] = 0; 189 | //Data chunk 190 | header[36] = 'd';//data 191 | header[37] = 'a'; 192 | header[38] = 't'; 193 | header[39] = 'a'; 194 | header[40] = (byte) (totalAudioLen & 0xff); 195 | header[41] = (byte) ((totalAudioLen >> 8) & 0xff); 196 | header[42] = (byte) ((totalAudioLen >> 16) & 0xff); 197 | header[43] = (byte) ((totalAudioLen >> 24) & 0xff); 198 | 199 | return header; 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/AudioMsg.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | /** 4 | */ 5 | 6 | public class AudioMsg { 7 | 8 | public String type; 9 | public String path; 10 | public String msg; 11 | 12 | public AudioMsg(String type, String path, String msg){ 13 | this.type = type; 14 | this.path = path; 15 | this.msg = msg; 16 | } 17 | 18 | public AudioMsg(String type, String msg){ 19 | this.type = type; 20 | this.msg = msg; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/AudioUtilHelper.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.content.Context; 4 | 5 | import com.audio.util.cut.AudioCutUtil; 6 | import com.audio.util.record.AudioRecordUtil; 7 | import com.audio.util.record.FinishCallback; 8 | import com.audio.util.record.RecordCallback; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.List; 13 | 14 | public class AudioUtilHelper { 15 | public static final String SUFFIX_WAV = ".wav"; 16 | public static final String TEMP = "_temp"; 17 | /** 18 | * 测试可用性方法 19 | * @return 20 | */ 21 | public static String helloStr(){ 22 | return "AudioUtilHelper 一个简单的测试字符串"; 23 | } 24 | /** 25 | * 获取录制音频下载本地临时存放文件路径 26 | */ 27 | public static String getRecordLocalTempFilePath(Context context,String name){ 28 | return getRecordDirectoryPath(context) + File.separator + name + TEMP + SUFFIX_WAV; 29 | } 30 | /** 31 | * 获取录制音频下载本地存放文件路径 32 | */ 33 | public static String getRecordLocalFilePath(Context context,String name){ 34 | return getRecordDirectoryPath(context) + File.separator + name + SUFFIX_WAV; 35 | } 36 | /** 37 | * 获取录制音频存放目录 38 | * @param context 39 | * @return 40 | */ 41 | public static String getRecordDirectoryPath(Context context){ 42 | return context.getExternalCacheDir().getAbsolutePath()+File.separator+"record"; 43 | } 44 | /** 45 | * 开始录音 46 | * wavOutFilePath wav录音文件输出路径 47 | */ 48 | public static void startRecord(Context context, String wavOutFilePath){ 49 | AudioRecordUtil.startRecord(context,wavOutFilePath); 50 | } 51 | /** 52 | * 开始音频录制 53 | * @param context 54 | * @param wavOutFilePath wavt音频文件输入路径 55 | * @param callback 56 | */ 57 | public static void startRecord(Context context, String wavOutFilePath, RecordCallback callback){ 58 | AudioRecordUtil.startRecord(context,wavOutFilePath,callback); 59 | } 60 | /** 61 | * 重新开始音频录制 62 | * @param context 63 | * @param wavOutFilePath wavt音频文件输入路径 64 | * @param callback 65 | */ 66 | public static void reStartRecord(Context context, String wavOutFilePath, RecordCallback callback){ 67 | AudioRecordUtil.reStartRecord(context,wavOutFilePath,callback); 68 | } 69 | /** 70 | * 开始录音 71 | * sampleRateInHz 采样率 72 | * channelConfig 声道数 73 | * audioFormat 采样位数 74 | * wavOutFilePath wav录音文件输出路径 75 | */ 76 | public static void startRecord(Context context, int sampleRateInHz, int channelConfig, int audioFormat,String wavOutFilePath) { 77 | AudioRecordUtil.startRecord(context,sampleRateInHz, channelConfig, audioFormat,wavOutFilePath,null); 78 | } 79 | /** 80 | * 开始录音 81 | * sampleRateInHz 采样率 82 | * channelConfig 声道数 83 | * audioFormat 采样位数 84 | * callback 录制回调 85 | * wavOutFilePath wav录音文件输出路径 86 | */ 87 | public static void startRecord(Context context,int sampleRateInHz,int channelConfig,int audioFormat,String wavOutFilePath,RecordCallback callback) { 88 | AudioRecordUtil.startRecord(context,sampleRateInHz, channelConfig, audioFormat,wavOutFilePath,callback); 89 | } 90 | 91 | /** 92 | * 暂停录音 93 | */ 94 | public static void pause(){ 95 | AudioRecordUtil.pause(); 96 | } 97 | 98 | /** 99 | * 完成录制; 100 | * 建议在子线程调用处理 101 | */ 102 | public static void finishRecord(FinishCallback callback){ 103 | AudioRecordUtil.finishRecord(callback); 104 | } 105 | /** 106 | * 完成录制; 107 | * 建议在子线程调用处理 108 | */ 109 | public static void finishRecord(){ 110 | AudioRecordUtil.finishRecord(null); 111 | } 112 | /** 113 | * 丢弃当前录制的音频 114 | */ 115 | public static void giveUpRecord(){ 116 | AudioRecordUtil.giveUp(); 117 | } 118 | /** 119 | * 获取媒体音频文件时长 120 | * @param url 音频文件存放url可以是本地文件也可以是网络文件 121 | * @return 122 | */ 123 | public static long getAudioDuration(String url){ 124 | return DecodeUtil.getAudioDuration(url); 125 | } 126 | /** 127 | * 把mp3文件转换为wav文件 128 | * @param inMp3Path 129 | * @param outWavPath 130 | * @param callback 131 | */ 132 | public static void mp3ToWav(String inMp3Path, String outWavPath, DecodeUtil.DecodeOperateInterface callback){ 133 | 134 | String pcmPath = inMp3Path + ".temp.pcm"; 135 | DecodeUtil.decodeAudio(inMp3Path, pcmPath, 0, -10, callback); 136 | 137 | ConvertUtil.convertPcm2WavBitNum16(pcmPath, outWavPath); 138 | new File(pcmPath).delete(); 139 | } 140 | 141 | /** 142 | * 音频合成功能 143 | * @param path1 音频文件输入路径 144 | * @param path2 音频文件输入路径 145 | * @param outPath 合成后的结果文件路径 146 | * @param progress1 音量比值 147 | * @param progress2 音量比值 148 | */ 149 | public static void mixAudio(String path1, String path2, String outPath,float progress1, float progress2){ 150 | MixAudioUtil.mixAudio(path1,path2,outPath,progress1,progress2); 151 | } 152 | /** 153 | * 音频合成功能 154 | * @param path1 音频文件输入路径 155 | * @param path2 音频文件输入路径 156 | * @param outPath 合成后的结果文件路径 157 | * @param progress1 音量比值 158 | * @param progress2 音量比值 159 | * @param callback 音频合成功能回调 160 | */ 161 | public static void mixAudio(String path1, String path2, String outPath, float progress1, float progress2, MixAudioUtil.MixAudioCallback callback){ 162 | MixAudioUtil.mixAudio(path1,path2,outPath,progress1,progress2,callback); 163 | } 164 | /** 165 | * 拼接wav音频的文件 166 | * @param inputs 167 | * @param output 168 | * @throws IOException 169 | */ 170 | public static void mergeWav(List inputs, File output) throws IOException { 171 | WavMergeUtil.mergeWav(inputs,output); 172 | } 173 | /** 174 | * 裁剪音频功能 175 | * @param srcPath 源音频路径 176 | * @param destPath 目标音频路径 177 | * @param startTime 裁剪开始时间 178 | * @param endTime 裁剪结束时间 179 | */ 180 | public static void cutAudio(String srcPath,String destPath, float startTime, float endTime){ 181 | AudioCutUtil.cutAudio(srcPath,destPath,startTime,endTime); 182 | } 183 | 184 | /** 185 | * 裁剪音频功能 186 | * @param srcPath 源音频路径 187 | *@param destPath 目标音频路径 188 | *@param startTime 裁剪开始时间 189 | * @param endTime 裁剪结束时间 190 | * @param callback 音频裁剪回调 191 | */ 192 | public static void cutAudio(String srcPath, String destPath, float startTime, float endTime, AudioCutUtil.AudioCutCallback callback){ 193 | AudioCutUtil.cutAudio(srcPath,destPath,startTime,endTime,callback); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | /** 4 | * 各基础类型与byte之间的转换 5 | */ 6 | public class ByteUtil { 7 | 8 | /** 9 | * 将short转成byte[2] 10 | */ 11 | public static byte[] short2Byte(short a) { 12 | byte[] b = new byte[2]; 13 | 14 | b[0] = (byte) (a >> 8); 15 | b[1] = (byte) (a); 16 | 17 | return b; 18 | } 19 | 20 | /** 21 | * 将short转成byte[2] 22 | * 23 | * @param offset b中的偏移量 24 | */ 25 | public static void short2Byte(short a, byte[] b, int offset) { 26 | b[offset] = (byte) (a >> 8); 27 | b[offset + 1] = (byte) (a); 28 | } 29 | 30 | /** 31 | * 将byte[2]转换成short 32 | */ 33 | public static short byte2Short(byte[] b) { 34 | return (short) (((b[0] & 0xff) << 8) | (b[1] & 0xff)); 35 | } 36 | 37 | /** 38 | * 将byte[2]转换成short 39 | */ 40 | public static short byte2Short(byte high, byte low) { 41 | return (short) (((high & 0xff) << 8) | (low & 0xff)); 42 | } 43 | 44 | /** 45 | * 将byte[2]转换成short 46 | */ 47 | public static short byte2Short(byte[] b, int offset) { 48 | return (short) (((b[offset] & 0xff) << 8) | (b[offset + 1] & 0xff)); 49 | } 50 | 51 | /** 52 | * long转byte[8] 53 | * 54 | * @param offset b的偏移量 55 | */ 56 | public static void long2Byte(long a, byte[] b, int offset) { 57 | b[offset + 0] = (byte) (a >> 56); 58 | b[offset + 1] = (byte) (a >> 48); 59 | b[offset + 2] = (byte) (a >> 40); 60 | b[offset + 3] = (byte) (a >> 32); 61 | 62 | b[offset + 4] = (byte) (a >> 24); 63 | b[offset + 5] = (byte) (a >> 16); 64 | b[offset + 6] = (byte) (a >> 8); 65 | b[offset + 7] = (byte) (a); 66 | } 67 | 68 | /** 69 | * byte[8]转long 70 | * 71 | * @param offset b的偏移量 72 | */ 73 | public static long byte2Long(byte[] b, int offset) { 74 | return ((((long) b[offset + 0] & 0xff) << 56) 75 | | (((long) b[offset + 1] & 0xff) << 48) 76 | | (((long) b[offset + 2] & 0xff) << 40) 77 | | (((long) b[offset + 3] & 0xff) << 32) 78 | 79 | | (((long) b[offset + 4] & 0xff) << 24) 80 | | (((long) b[offset + 5] & 0xff) << 16) 81 | | (((long) b[offset + 6] & 0xff) << 8) 82 | | (((long) b[offset + 7] & 0xff) << 0)); 83 | } 84 | 85 | /** 86 | * byte[8]转long 87 | */ 88 | public static long byte2Long(byte[] b) { 89 | return ((b[0] & 0xff) << 56) | ((b[1] & 0xff) << 48) | ((b[2] & 0xff) << 40) | ((b[3] & 0xff) 90 | << 32) | 91 | 92 | ((b[4] & 0xff) << 24) | ((b[5] & 0xff) << 16) | ((b[6] & 0xff) << 8) | (b[7] & 0xff); 93 | } 94 | 95 | /** 96 | * long转byte[8] 97 | */ 98 | public static byte[] long2Byte(long a) { 99 | byte[] b = new byte[4 * 2]; 100 | 101 | b[0] = (byte) (a >> 56); 102 | b[1] = (byte) (a >> 48); 103 | b[2] = (byte) (a >> 40); 104 | b[3] = (byte) (a >> 32); 105 | 106 | b[4] = (byte) (a >> 24); 107 | b[5] = (byte) (a >> 16); 108 | b[6] = (byte) (a >> 8); 109 | b[7] = (byte) (a >> 0); 110 | 111 | return b; 112 | } 113 | 114 | /** 115 | * byte数组转int 116 | */ 117 | public static int byte2Int(byte[] b) { 118 | return ((b[0] & 0xff) << 24) | ((b[1] & 0xff) << 16) | ((b[2] & 0xff) << 8) | (b[3] & 0xff); 119 | } 120 | 121 | /** 122 | * byte数组转int 123 | */ 124 | public static int byte2Int(byte[] b, int offset) { 125 | return ((b[offset++] & 0xff) << 24) 126 | | ((b[offset++] & 0xff) << 16) 127 | | ((b[offset++] & 0xff) << 8) 128 | | (b[offset++] & 0xff); 129 | } 130 | 131 | /** 132 | * int转byte数组 133 | */ 134 | public static byte[] int2Byte(int a) { 135 | byte[] b = new byte[4]; 136 | b[0] = (byte) (a >> 24); 137 | b[1] = (byte) (a >> 16); 138 | b[2] = (byte) (a >> 8); 139 | b[3] = (byte) (a); 140 | 141 | return b; 142 | } 143 | 144 | /** 145 | * int转byte数组 146 | */ 147 | public static void int2Byte(int a, byte[] b, int offset) { 148 | b[offset++] = (byte) (a >> 24); 149 | b[offset++] = (byte) (a >> 16); 150 | b[offset++] = (byte) (a >> 8); 151 | b[offset++] = (byte) (a); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/ComposeInfo.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | /** 4 | * 音频合成信息 5 | * 6 | */ 7 | public class ComposeInfo { 8 | 9 | /** 10 | * 音频文件路径 11 | */ 12 | public String audioPath; 13 | 14 | /** 15 | * 音频解码后的pcm文件路径 16 | */ 17 | public String pcmPath; 18 | 19 | /** 20 | * 音频开始播放的时间 21 | */ 22 | public float offsetSeconds; 23 | 24 | /** 25 | * 参与合成的权重大小 26 | */ 27 | public float weight; 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/ConvertUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.audio.util.util.Constant; 6 | 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | 11 | /** 12 | * 文件转换工具类 13 | */ 14 | public class ConvertUtil { 15 | /** 16 | * PCM文件转WAV文件; 17 | * 采样位数为16 18 | * @param inPcmFilePath 19 | * @param outWavFilePath 20 | */ 21 | public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath) { 22 | convertPcm2Wav(inPcmFilePath, outWavFilePath, Constant.ExportSampleRate, 23 | Constant.ExportChannelNumber, 16); 24 | } 25 | 26 | /** 27 | * PCM文件转WAV文件; 28 | * 采样位数为8 29 | * @param inPcmFilePath 30 | * @param outWavFilePath 31 | */ 32 | public static void convertPcm2Wav2(String inPcmFilePath, String outWavFilePath) { 33 | convertPcm2Wav(inPcmFilePath, outWavFilePath, Constant.ExportSampleRate, 34 | Constant.ExportChannelNumber, 8); 35 | } 36 | /** 37 | * PCM文件转WAV文件 38 | * @param inPcmFilePath 输入PCM文件路径 39 | * @param outWavFilePath 输出WAV文件路径 40 | * @param sampleRate 采样率,例如44100 41 | * @param channels 声道数 单声道:1或双声道:2 42 | * @param bitNum 采样位数,8或16 43 | */ 44 | public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate, 45 | int channels, int bitNum) { 46 | FileInputStream in = null; 47 | FileOutputStream out = null; 48 | byte[] data = new byte[1024]; 49 | 50 | try{ 51 | //采样字节byte率 52 | long byteRate = sampleRate * channels * bitNum / 8; 53 | 54 | in = new FileInputStream(inPcmFilePath); 55 | out = new FileOutputStream(outWavFilePath); 56 | 57 | //PCM文件大小 58 | long totalAudioLen = in.getChannel().size(); 59 | 60 | //总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小 61 | long totalDataLen = totalAudioLen + 36; 62 | 63 | writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate); 64 | 65 | int length = 0; 66 | while ((length = in.read(data)) > 0) { 67 | out.write(data, 0, length); 68 | } 69 | }catch (Exception e){ 70 | e.printStackTrace(); 71 | }finally { 72 | if (in != null) { 73 | try { 74 | in.close(); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | if (out != null) { 80 | try { 81 | out.close(); 82 | } catch (IOException e) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | Log.d("ddebug","转换完成了"); 87 | } 88 | 89 | } 90 | /** 91 | * 输出WAV文件 92 | * @param out WAV输出文件流 93 | * @param totalAudioLen 整个音频PCM数据大小 94 | * @param totalDataLen 整个数据大小 95 | * @param sampleRate 采样率 96 | * @param channels 声道数 97 | * @param byteRate 采样字节byte率 98 | * @throws IOException 99 | */ 100 | private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen, 101 | long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException { 102 | byte[] header = new byte[44]; 103 | header[0] = 'R'; // RIFF 104 | header[1] = 'I'; 105 | header[2] = 'F'; 106 | header[3] = 'F'; 107 | header[4] = (byte) (totalDataLen & 0xff);//数据大小 108 | header[5] = (byte) ((totalDataLen >> 8) & 0xff); 109 | header[6] = (byte) ((totalDataLen >> 16) & 0xff); 110 | header[7] = (byte) ((totalDataLen >> 24) & 0xff); 111 | header[8] = 'W';//WAVE 112 | header[9] = 'A'; 113 | header[10] = 'V'; 114 | header[11] = 'E'; 115 | //FMT Chunk 116 | header[12] = 'f'; // 'fmt ' 117 | header[13] = 'm'; 118 | header[14] = 't'; 119 | header[15] = ' ';//过渡字节 120 | //数据大小 121 | header[16] = 16; // 4 bytes: size of 'fmt ' chunk 122 | header[17] = 0; 123 | header[18] = 0; 124 | header[19] = 0; 125 | //编码方式 10H为PCM编码格式 126 | header[20] = 1; // format = 1 127 | header[21] = 0; 128 | //通道数 129 | header[22] = (byte) channels; 130 | header[23] = 0; 131 | //采样率,每个通道的播放速度 132 | header[24] = (byte) (sampleRate & 0xff); 133 | header[25] = (byte) ((sampleRate >> 8) & 0xff); 134 | header[26] = (byte) ((sampleRate >> 16) & 0xff); 135 | header[27] = (byte) ((sampleRate >> 24) & 0xff); 136 | //音频数据传送速率,采样率*通道数*采样深度/8 137 | header[28] = (byte) (byteRate & 0xff); 138 | header[29] = (byte) ((byteRate >> 8) & 0xff); 139 | header[30] = (byte) ((byteRate >> 16) & 0xff); 140 | header[31] = (byte) ((byteRate >> 24) & 0xff); 141 | // 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数 142 | header[32] = (byte) (channels * 16 / 8); 143 | header[33] = 0; 144 | //每个样本的数据位数 145 | header[34] = 16; 146 | header[35] = 0; 147 | //Data chunk 148 | header[36] = 'd';//data 149 | header[37] = 'a'; 150 | header[38] = 't'; 151 | header[39] = 'a'; 152 | header[40] = (byte) (totalAudioLen & 0xff); 153 | header[41] = (byte) ((totalAudioLen >> 8) & 0xff); 154 | header[42] = (byte) ((totalAudioLen >> 16) & 0xff); 155 | header[43] = (byte) ((totalAudioLen >> 24) & 0xff); 156 | out.write(header, 0, 44); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/DecodeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.os.Environment 6 | import android.util.Log 7 | import android.view.View 8 | import com.audio.util.DecodeUtil.DecodeOperateInterface 9 | import com.audio.util.cut.AudioCutUtil 10 | import kotlinx.android.synthetic.main.activity_decode.* 11 | import java.io.File 12 | 13 | class DecodeActivity : AppCompatActivity() { 14 | val callback = object : DecodeOperateInterface { 15 | override fun onLoadedAudioInfo(audio: String?) { 16 | tv_info.append(audio) 17 | } 18 | 19 | override fun onLoadedError(audio: String?) { 20 | tv_info.append(audio) 21 | } 22 | 23 | } 24 | 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_decode) 29 | 30 | } 31 | fun start(v: View){ 32 | // var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/提现成功.m4a" 33 | // var pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/提现成功${System.currentTimeMillis()}.m4a" 34 | // DecodeUtil.decodeAudio(path,pathTaret,0,10,callback) 35 | 36 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你.mp3" 37 | var pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你${System.currentTimeMillis()}.pcm" 38 | DecodeUtil.decodeAudio(path, pathTaret, 0, -10, callback) 39 | 40 | // path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘.mp3" 41 | // pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘${System.currentTimeMillis()}.mp3" 42 | // DecodeUtil.decodeAudio(path,pathTaret,0,-10,callback) 43 | } 44 | fun convert(v:View){ 45 | // var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘1610188365903.pcm" 46 | // var pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘convert${System.currentTimeMillis()}.wav" 47 | // ConvertUtil.convertPcm2Wav2(path,pathTaret) 48 | 49 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你1610210733753.pcm" 50 | var pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert${System.currentTimeMillis()}.wav" 51 | ConvertUtil.convertPcm2Wav2(path, pathTaret) 52 | 53 | } 54 | fun mixAudio(v:View){ 55 | 56 | 57 | object : Thread(){ 58 | override fun run() { 59 | super.run() 60 | var path1 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 61 | var path2 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘convert1610201261595.wav" 62 | var pathOut = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘_姑娘我爱你_mix.wav" 63 | //MixAudioUtil.mixAudio(path1, path2, pathOut, 0.3f, 0.5f); 64 | AudioUtilHelper.mixAudio(path1, path2, pathOut, 0.3f, 0.5f) 65 | Log.d("ddebug","---run---") 66 | } 67 | }.start(); 68 | 69 | } 70 | 71 | fun mergeAudio(v:View){ 72 | log("---mergeAudio---") 73 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘convert1610201261595.wav" 74 | var path1 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 75 | var pathOut = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/遇上你是我的缘merge.wav" 76 | val fileIn = File(path) 77 | val fileOut = File(pathOut); 78 | val list = listOf(fileIn, File(path1)) 79 | 80 | Thread(){ 81 | log("---mergeAudio---") 82 | //WavMergeUtil.mergeWav(list, fileOut); 83 | AudioUtilHelper.mergeWav(list,fileOut) 84 | }.start(); 85 | 86 | } 87 | 88 | fun cutAudio(v:View){ 89 | Thread(){ 90 | log("---cutAudio---") 91 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 92 | var path2 = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你cut1.wav" 93 | //AudioCutUtil.cutAudio(path,path2,20f,30f) 94 | AudioUtilHelper.cutAudio(path,path2,20f,30f); 95 | }.start(); 96 | } 97 | fun cutAudio2(v:View){ 98 | Thread(){ 99 | log("---cutAudio---") 100 | var path = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert1610210798289.wav" 101 | var pathOut = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你cut2.wav" 102 | // AudioCutUtil.cutAudio(path,pathOut,60f,75f,object: AudioCutUtil.AudioCutCallback{ 103 | // override fun onFinish(pathFinish: String?, msg: String?) { 104 | // Log.d("ddebug","act onFinish $pathFinish $msg") 105 | // } 106 | // 107 | // override fun onCutError(msg: String?) { 108 | // Log.d("ddebug","act onCutError $msg") 109 | // } 110 | // 111 | // }) 112 | AudioUtilHelper.cutAudio(path,pathOut,60f,75f,object: AudioCutUtil.AudioCutCallback{ 113 | override fun onFinish(pathFinish: String?, msg: String?) { 114 | Log.d("ddebug","act onFinish $pathFinish $msg") 115 | } 116 | 117 | override fun onCutError(msg: String?) { 118 | Log.d("ddebug","act onCutError $msg") 119 | } 120 | 121 | }) 122 | }.start(); 123 | } 124 | 125 | fun log(msg:String){ 126 | Log.d("ddebug",msg) 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/DecodeEngine.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.media.AudioFormat; 5 | import android.media.MediaCodec; 6 | import android.media.MediaExtractor; 7 | import android.media.MediaFormat; 8 | import android.os.Build; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.text.TextUtils; 12 | 13 | import com.audio.util.callback.DecodeOperateInterface; 14 | import com.audio.util.encode.CommonFunction; 15 | import com.audio.util.ssrc.SSRC; 16 | import com.audio.util.util.Constant; 17 | 18 | import java.io.BufferedOutputStream; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * 音频文件解码 27 | */ 28 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class DecodeEngine { 29 | private static DecodeEngine instance; 30 | 31 | private DecodeEngine() { 32 | } 33 | 34 | public static DecodeEngine getInstance() { 35 | if (instance == null) { 36 | synchronized (DecodeEngine.class) { 37 | if (instance == null) { 38 | instance = new DecodeEngine(); 39 | } 40 | } 41 | } 42 | 43 | return instance; 44 | } 45 | 46 | public void beginDecodeMusicFile(String musicFileUrl, String decodeFileUrl, int startSecond, 47 | int endSecond, final DecodeOperateInterface decodeOperateInterface) { 48 | Handler handler = new Handler(Looper.getMainLooper()); 49 | 50 | final boolean decodeResult = 51 | decodeMusicFile(musicFileUrl, decodeFileUrl, true, startSecond, endSecond, 52 | decodeOperateInterface); 53 | 54 | handler.post(new Runnable() { 55 | @Override public void run() { 56 | if (decodeResult) { 57 | decodeOperateInterface.decodeSuccess(); 58 | } else { 59 | decodeOperateInterface.decodeFail(); 60 | } 61 | } 62 | }); 63 | } 64 | 65 | /** 66 | * 将音乐文件解码为wav文件 67 | * 68 | * @param musicFileUrl 源文件路径 69 | * @param decodeFileUrl 转换文件路径 70 | * @param decodeOperateInterface 解码过程回调 71 | */ 72 | public boolean convertMusicFileToWaveFile(String musicFileUrl, String decodeFileUrl, 73 | DecodeOperateInterface decodeOperateInterface) { 74 | 75 | boolean success = 76 | decodeMusicFile(musicFileUrl, decodeFileUrl, true, -1, -1, decodeOperateInterface); 77 | if (decodeOperateInterface != null) { 78 | if (success) { 79 | decodeOperateInterface.decodeSuccess(); 80 | } else { 81 | decodeOperateInterface.decodeFail(); 82 | } 83 | } 84 | return success; 85 | } 86 | 87 | /** 88 | * 将音乐文件解码为wav文件 89 | * 90 | * @param musicFileUrl 源文件路径 91 | * @param decodeFileUrl 转换文件路径 92 | * @param startSecond 开始时间 秒 93 | * @param endSecond 结束时间 秒 94 | * @param decodeOperateInterface 解码过程回调 95 | */ 96 | public boolean convertMusicFileToWaveFile(String musicFileUrl, String decodeFileUrl, 97 | double startSecond, double endSecond, DecodeOperateInterface decodeOperateInterface) { 98 | 99 | boolean success = 100 | decodeMusicFile(musicFileUrl, decodeFileUrl, true, (long) startSecond * 1000000, 101 | (long) endSecond * 1000000, decodeOperateInterface); 102 | if (decodeOperateInterface != null) { 103 | if (success) { 104 | decodeOperateInterface.decodeSuccess(); 105 | } else { 106 | decodeOperateInterface.decodeFail(); 107 | } 108 | } 109 | 110 | return success; 111 | } 112 | 113 | /** 114 | * 将音乐文件解码为pcm文件 115 | * 116 | * @param musicFileUrl 源文件路径 117 | * @param decodeFileUrl 转换文件路径 118 | * @param decodeOperateInterface 解码过程回调 119 | */ 120 | public boolean convertMusicFileToPcmFile(String musicFileUrl, String decodeFileUrl, 121 | DecodeOperateInterface decodeOperateInterface) { 122 | 123 | boolean success = 124 | decodeMusicFile(musicFileUrl, decodeFileUrl, false, -1, -1, decodeOperateInterface); 125 | if (decodeOperateInterface != null) { 126 | if (success) { 127 | decodeOperateInterface.decodeSuccess(); 128 | } else { 129 | decodeOperateInterface.decodeFail(); 130 | } 131 | } 132 | 133 | return success; 134 | } 135 | 136 | /** 137 | * 将音乐文件解码为pcm文件 138 | * 139 | * @param musicFileUrl 源文件路径 140 | * @param decodeFileUrl 转换文件路径 141 | * @param startSecond 开始时间 秒 142 | * @param endSecond 结束时间 秒 143 | * @param decodeOperateInterface 解码过程回调 144 | */ 145 | public boolean convertMusicFileToPcmFile(String musicFileUrl, String decodeFileUrl, 146 | int startSecond, int endSecond, DecodeOperateInterface decodeOperateInterface) { 147 | 148 | boolean success = 149 | decodeMusicFile(musicFileUrl, decodeFileUrl, false, (long) startSecond * 1000000, 150 | (long) endSecond * 1000000, decodeOperateInterface); 151 | if (decodeOperateInterface != null) { 152 | if (success) { 153 | decodeOperateInterface.decodeSuccess(); 154 | } else { 155 | decodeOperateInterface.decodeFail(); 156 | } 157 | } 158 | 159 | return success; 160 | } 161 | 162 | /** 163 | * 将音乐文件解码 164 | * 165 | * @param musicFileUrl 源文件路径 166 | * @param decodeFileUrl 解码文件路径 167 | * @param startMicroseconds 开始时间 微秒 168 | * @param endMicroseconds 结束时间 微秒 169 | * @param decodeOperateInterface 解码过程回调 170 | */ 171 | private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl, 172 | long startMicroseconds, long endMicroseconds, DecodeOperateInterface decodeOperateInterface) { 173 | 174 | //采样率,声道数,时长,音频文件类型 175 | int sampleRate = 0; 176 | int channelCount = 0; 177 | long duration = 0; 178 | String mime = null; 179 | 180 | //MediaExtractor, MediaFormat, MediaCodec 181 | MediaExtractor mediaExtractor = new MediaExtractor(); 182 | MediaFormat mediaFormat = null; 183 | MediaCodec mediaCodec = null; 184 | 185 | //给媒体信息提取器设置源音频文件路径 186 | try { 187 | mediaExtractor.setDataSource(musicFileUrl); 188 | }catch (Exception ex){ 189 | ex.printStackTrace(); 190 | try { 191 | mediaExtractor.setDataSource(new FileInputStream(musicFileUrl).getFD()); 192 | } catch (Exception e) { 193 | e.printStackTrace(); 194 | LogUtil.e("设置解码音频文件路径错误"); 195 | } 196 | } 197 | 198 | //获取音频格式轨信息 199 | mediaFormat = mediaExtractor.getTrackFormat(0); 200 | 201 | //从音频格式轨信息中读取 采样率,声道数,时长,音频文件类型 202 | sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? mediaFormat.getInteger( 203 | MediaFormat.KEY_SAMPLE_RATE) : 44100; 204 | channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? mediaFormat.getInteger( 205 | MediaFormat.KEY_CHANNEL_COUNT) : 1; 206 | duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong( 207 | MediaFormat.KEY_DURATION) : 0; 208 | mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) 209 | : ""; 210 | 211 | LogUtil.i("歌曲信息Track info: mime:" 212 | + mime 213 | + " 采样率sampleRate:" 214 | + sampleRate 215 | + " channels:" 216 | + channelCount 217 | + " duration:" 218 | + duration); 219 | 220 | if (TextUtils.isEmpty(mime) || !mime.startsWith("audio/")) { 221 | LogUtil.e("解码文件不是音频文件mime:" + mime); 222 | return false; 223 | } 224 | 225 | if (mime.equals("audio/ffmpeg")) { 226 | mime = "audio/mpeg"; 227 | mediaFormat.setString(MediaFormat.KEY_MIME, mime); 228 | } 229 | 230 | if (duration <= 0) { 231 | LogUtil.e("音频文件duration为" + duration); 232 | return false; 233 | } 234 | 235 | //解码的开始时间和结束时间 236 | startMicroseconds = Math.max(startMicroseconds, 0); 237 | endMicroseconds = endMicroseconds < 0 ? duration : endMicroseconds; 238 | endMicroseconds = Math.min(endMicroseconds, duration); 239 | 240 | if (startMicroseconds >= endMicroseconds) { 241 | return false; 242 | } 243 | 244 | //创建一个解码器 245 | try { 246 | mediaCodec = MediaCodec.createDecoderByType(mime); 247 | 248 | mediaCodec.configure(mediaFormat, null, null, 0); 249 | } catch (Exception e) { 250 | LogUtil.e("解码器configure出错"); 251 | return false; 252 | } 253 | 254 | //得到输出PCM文件的路径 255 | decodeFileUrl = decodeFileUrl.substring(0, decodeFileUrl.lastIndexOf(".")); 256 | String pcmFilePath = decodeFileUrl + ".pcm"; 257 | 258 | //后续解码操作 259 | getDecodeData(mediaExtractor, mediaCodec, pcmFilePath, sampleRate, channelCount, 260 | startMicroseconds, endMicroseconds, decodeOperateInterface); 261 | 262 | return true; 263 | } 264 | 265 | /** 266 | * 将音乐文件解码 267 | * 268 | * @param musicFileUrl 源文件路径 269 | * @param decodeFileUrl 解码文件路径 270 | * @param isWave 是解码为wave文件, 否解码为pcm文件 271 | * @param startMicroseconds 开始时间 微秒 272 | * @param endMicroseconds 结束时间 微秒 273 | * @param decodeOperateInterface 解码过程回调 274 | */ 275 | private boolean decodeMusicFile(String musicFileUrl, String decodeFileUrl, boolean isWave, 276 | long startMicroseconds, long endMicroseconds, DecodeOperateInterface decodeOperateInterface) { 277 | 278 | int sampleRate = 0; 279 | int channelCount = 0; 280 | int bitNumber = 0; 281 | long duration = 0; 282 | String mime = null; 283 | MediaExtractor mediaExtractor = new MediaExtractor(); 284 | MediaFormat mediaFormat = null; 285 | MediaCodec mediaCodec = null; 286 | 287 | try { 288 | mediaExtractor.setDataSource(musicFileUrl); 289 | }catch (Exception ex){ 290 | ex.printStackTrace(); 291 | try { 292 | mediaExtractor.setDataSource(new FileInputStream(musicFileUrl).getFD()); 293 | } catch (Exception e) { 294 | e.printStackTrace(); 295 | LogUtil.e("设置解码音频文件路径错误"); 296 | } 297 | } 298 | 299 | mediaFormat = mediaExtractor.getTrackFormat(0); 300 | sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? mediaFormat.getInteger( MediaFormat.KEY_SAMPLE_RATE) : 44100; 301 | channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1; 302 | duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong(MediaFormat.KEY_DURATION) : 0; 303 | mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) : ""; 304 | 305 | //根据pcmEncoding编码格式,得到采样精度,MediaFormat.KEY_PCM_ENCODING这个值不一定有 306 | int pcmEncoding = mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING) ? mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING) : AudioFormat.ENCODING_PCM_16BIT; 307 | switch (pcmEncoding){ 308 | case AudioFormat.ENCODING_PCM_FLOAT: 309 | bitNumber = 32; 310 | break; 311 | case AudioFormat.ENCODING_PCM_8BIT: 312 | bitNumber = 8; 313 | break; 314 | case AudioFormat.ENCODING_PCM_16BIT: 315 | default: 316 | bitNumber = 16; 317 | break; 318 | } 319 | 320 | LogUtil.i("歌曲信息Track info: mime:" 321 | + mime 322 | + " 采样率sampleRate:" 323 | + sampleRate 324 | + " channels:" 325 | + channelCount 326 | + " duration:" 327 | + duration); 328 | 329 | if (TextUtils.isEmpty(mime) || !mime.startsWith("audio/")) { 330 | LogUtil.e("解码文件不是音频文件mime:" + mime); 331 | return false; 332 | } 333 | 334 | if (mime.equals("audio/ffmpeg")) { 335 | mime = "audio/mpeg"; 336 | mediaFormat.setString(MediaFormat.KEY_MIME, mime); 337 | } 338 | 339 | if (duration <= 0) { 340 | LogUtil.e("音频文件duration为" + duration); 341 | return false; 342 | } 343 | 344 | //开始时间和结束时间 345 | startMicroseconds = Math.max(startMicroseconds, 0); 346 | endMicroseconds = endMicroseconds < 0 ? duration : endMicroseconds; 347 | endMicroseconds = Math.min(endMicroseconds, duration); 348 | 349 | if (startMicroseconds >= endMicroseconds) { 350 | return false; 351 | } 352 | 353 | try { 354 | mediaCodec = MediaCodec.createDecoderByType(mime); 355 | 356 | mediaCodec.configure(mediaFormat, null, null, 0); 357 | } catch (Exception e) { 358 | LogUtil.e("解码器configure出错"); 359 | return false; 360 | } 361 | 362 | decodeFileUrl = decodeFileUrl.substring(0, decodeFileUrl.lastIndexOf(".")); 363 | String pcmFilePath = decodeFileUrl + ".pcm"; 364 | String wavFilePath = decodeFileUrl + ".wav"; 365 | getDecodeData(mediaExtractor, mediaCodec, pcmFilePath, sampleRate, channelCount, 366 | startMicroseconds, endMicroseconds, decodeOperateInterface); 367 | 368 | if (isWave) { 369 | convertPcmFileToWaveFile(pcmFilePath, wavFilePath, sampleRate, channelCount, bitNumber); 370 | 371 | new File(pcmFilePath).delete(); 372 | } 373 | 374 | return true; 375 | } 376 | 377 | /** 378 | * 将pcm文件转为wave文件 379 | * 380 | * @param pcmFilePath 源pcm文件路径 381 | * @param waveFilePath 目标wave文件路径 382 | * @param channels 声道数 383 | * @param bitNumber 采样位数,8或16 384 | */ 385 | private void convertPcmFileToWaveFile(String pcmFilePath, String waveFilePath, int sampleRate, 386 | int channels, int bitNumber) { 387 | AudioEncodeUtil.convertPcm2Wav(pcmFilePath, waveFilePath, sampleRate, channels, bitNumber); 388 | } 389 | 390 | /** 391 | * 解码数据 392 | */ 393 | private void getDecodeData(MediaExtractor mediaExtractor, MediaCodec mediaCodec, 394 | String decodeFileUrl, int sampleRate, int channelCount, final long startMicroseconds, 395 | final long endMicroseconds, final DecodeOperateInterface decodeOperateInterface) { 396 | 397 | //初始化解码状态,未解析完成 398 | boolean decodeInputEnd = false; 399 | boolean decodeOutputEnd = false; 400 | 401 | //当前读取采样数据的大小 402 | int sampleDataSize; 403 | //当前输入数据的ByteBuffer序号,当前输出数据的ByteBuffer序号 404 | int inputBufferIndex; 405 | int outputBufferIndex; 406 | //音频文件的采样位数字节数,= 采样位数/8 407 | int byteNumber; 408 | 409 | //上一次的解码操作时间,当前解码操作时间,用于通知回调接口 410 | long decodeNoticeTime = System.currentTimeMillis(); 411 | long decodeTime; 412 | 413 | //当前采样的音频时间,比如在当前音频的第40秒的时候 414 | long presentationTimeUs = 0; 415 | 416 | //定义编解码的超时时间 417 | final long timeOutUs = 100; 418 | 419 | //存储输入数据的ByteBuffer数组,输出数据的ByteBuffer数组 420 | ByteBuffer[] inputBuffers; 421 | ByteBuffer[] outputBuffers; 422 | 423 | //当前编解码器操作的 输入数据ByteBuffer 和 输出数据ByteBuffer,可以从targetBuffer中获取解码后的PCM数据 424 | ByteBuffer sourceBuffer; 425 | ByteBuffer targetBuffer; 426 | 427 | //获取输出音频的媒体格式信息 428 | MediaFormat outputFormat = mediaCodec.getOutputFormat(); 429 | 430 | MediaCodec.BufferInfo bufferInfo; 431 | 432 | byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8; 433 | 434 | //开始解码操作 435 | mediaCodec.start(); 436 | 437 | //获取存储输入数据的ByteBuffer数组,输出数据的ByteBuffer数组 438 | inputBuffers = mediaCodec.getInputBuffers(); 439 | outputBuffers = mediaCodec.getOutputBuffers(); 440 | 441 | mediaExtractor.selectTrack(0); 442 | 443 | //当前解码的缓存信息,里面的有效数据在offset和offset+size之间 444 | bufferInfo = new MediaCodec.BufferInfo(); 445 | 446 | //获取解码后文件的输出流 447 | BufferedOutputStream bufferedOutputStream = 448 | FileFunction.getBufferedOutputStreamFromFile(decodeFileUrl); 449 | 450 | //开始进入循环解码操作,判断读入源音频数据是否完成,输出解码音频数据是否完成 451 | while (!decodeOutputEnd) { 452 | if (decodeInputEnd) { 453 | return; 454 | } 455 | 456 | decodeTime = System.currentTimeMillis(); 457 | 458 | //间隔1秒通知解码进度 459 | if (decodeTime - decodeNoticeTime > Constant.OneSecond) { 460 | final int decodeProgress = 461 | (int) ((presentationTimeUs - startMicroseconds) * Constant.NormalMaxProgress 462 | / endMicroseconds); 463 | 464 | if (decodeProgress > 0) { 465 | notifyProgress(decodeOperateInterface, decodeProgress); 466 | } 467 | 468 | decodeNoticeTime = decodeTime; 469 | } 470 | 471 | try { 472 | 473 | //操作解码输入数据 474 | 475 | //从队列中获取当前解码器处理输入数据的ByteBuffer序号 476 | inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs); 477 | 478 | if (inputBufferIndex >= 0) { 479 | //取得当前解码器处理输入数据的ByteBuffer 480 | sourceBuffer = inputBuffers[inputBufferIndex]; 481 | //获取当前ByteBuffer,编解码器读取了多少采样数据 482 | sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0); 483 | 484 | //如果当前读取的采样数据<0,说明已经完成了读取操作 485 | if (sampleDataSize < 0) { 486 | decodeInputEnd = true; 487 | sampleDataSize = 0; 488 | } else { 489 | presentationTimeUs = mediaExtractor.getSampleTime(); 490 | } 491 | 492 | //然后将当前ByteBuffer重新加入到队列中交给编解码器做下一步读取操作 493 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize, presentationTimeUs, 494 | decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 495 | 496 | //前进到下一段采样数据 497 | if (!decodeInputEnd) { 498 | mediaExtractor.advance(); 499 | } 500 | 501 | } else { 502 | //LogUtil.e("inputBufferIndex" + inputBufferIndex); 503 | } 504 | 505 | //操作解码输出数据 506 | 507 | //从队列中获取当前解码器处理输出数据的ByteBuffer序号 508 | outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs); 509 | 510 | if (outputBufferIndex < 0) { 511 | //输出ByteBuffer序号<0,可能是输出缓存变化了,输出格式信息变化了 512 | switch (outputBufferIndex) { 513 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 514 | outputBuffers = mediaCodec.getOutputBuffers(); 515 | LogUtil.e( 516 | "MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED [AudioDecoder]output buffers have changed."); 517 | break; 518 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 519 | outputFormat = mediaCodec.getOutputFormat(); 520 | 521 | sampleRate = 522 | outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? outputFormat.getInteger( 523 | MediaFormat.KEY_SAMPLE_RATE) : sampleRate; 524 | channelCount = 525 | outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? outputFormat.getInteger( 526 | MediaFormat.KEY_CHANNEL_COUNT) : channelCount; 527 | byteNumber = 528 | (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) 529 | / 8; 530 | 531 | LogUtil.e( 532 | "MediaCodec.INFO_OUTPUT_FORMAT_CHANGED [AudioDecoder]output format has changed to " 533 | + mediaCodec.getOutputFormat()); 534 | break; 535 | default: 536 | //LogUtil.e("error [AudioDecoder] dequeueOutputBuffer returned " + outputBufferIndex); 537 | break; 538 | } 539 | continue; 540 | } 541 | 542 | //取得当前解码器处理输出数据的ByteBuffer 543 | targetBuffer = outputBuffers[outputBufferIndex]; 544 | 545 | byte[] sourceByteArray = new byte[bufferInfo.size]; 546 | 547 | //将解码后的targetBuffer中的数据复制到sourceByteArray中 548 | targetBuffer.get(sourceByteArray); 549 | targetBuffer.clear(); 550 | 551 | //释放当前的输出缓存 552 | mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 553 | 554 | //判断当前是否解码数据全部结束了 555 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 556 | decodeOutputEnd = true; 557 | } 558 | 559 | //sourceByteArray就是最终解码后的采样数据 560 | //接下来可以对这些数据进行采样位数,声道的转换,但这是可选的,默认是和源音频一样的声道和采样位数 561 | if (sourceByteArray.length > 0 && bufferedOutputStream != null) { 562 | if (presentationTimeUs < startMicroseconds) { 563 | continue; 564 | } 565 | 566 | //采样位数转换,按自己需要是否实现 567 | byte[] convertByteNumberByteArray = 568 | convertByteNumber(byteNumber, Constant.ExportByteNumber, sourceByteArray); 569 | 570 | //声道转换,按自己需要是否实现 571 | byte[] resultByteArray = convertChannelNumber(channelCount, Constant.ExportChannelNumber, 572 | Constant.ExportByteNumber, convertByteNumberByteArray); 573 | 574 | //将解码后的PCM数据写入到PCM文件 575 | try { 576 | bufferedOutputStream.write(resultByteArray); 577 | } catch (Exception e) { 578 | LogUtil.e("输出解压音频数据异常" + e); 579 | } 580 | } 581 | 582 | if (presentationTimeUs > endMicroseconds) { 583 | break; 584 | } 585 | } catch (Exception e) { 586 | LogUtil.e("getDecodeData异常" + e); 587 | } 588 | } 589 | 590 | if (bufferedOutputStream != null) { 591 | try { 592 | bufferedOutputStream.close(); 593 | } catch (IOException e) { 594 | LogUtil.e("关闭bufferedOutputStream异常" + e); 595 | } 596 | } 597 | 598 | //重置采样率 599 | if (sampleRate != Constant.ExportSampleRate) { 600 | Resample(sampleRate, decodeFileUrl); 601 | } 602 | 603 | notifyProgress(decodeOperateInterface, 100); 604 | 605 | //释放mediaCodec 和 mediaExtractor 606 | if (mediaCodec != null) { 607 | mediaCodec.stop(); 608 | mediaCodec.release(); 609 | } 610 | 611 | if (mediaExtractor != null) { 612 | mediaExtractor.release(); 613 | } 614 | } 615 | 616 | /** 617 | * 重置采样率 618 | */ 619 | private static void Resample(int sampleRate, String decodeFileUrl) { 620 | String newDecodeFileUrl = decodeFileUrl + "new"; 621 | 622 | try { 623 | FileInputStream fileInputStream = new FileInputStream(new File(decodeFileUrl)); 624 | FileOutputStream fileOutputStream = new FileOutputStream(new File(newDecodeFileUrl)); 625 | 626 | new SSRC(fileInputStream, fileOutputStream, sampleRate, Constant.ExportSampleRate, 627 | Constant.ExportByteNumber, Constant.ExportByteNumber, 1, Integer.MAX_VALUE, 0, 0, true); 628 | 629 | fileInputStream.close(); 630 | fileOutputStream.close(); 631 | 632 | FileFunction.renameFile(newDecodeFileUrl, decodeFileUrl); 633 | } catch (IOException e) { 634 | LogUtil.e("关闭bufferedOutputStream异常" + e); 635 | } 636 | } 637 | 638 | /** 639 | * 重置采样点字节数 640 | */ 641 | public static byte[] convertByteNumber(int sourceByteNumber, int outputByteNumber, 642 | byte[] sourceByteArray) { 643 | if (sourceByteNumber == outputByteNumber) { 644 | return sourceByteArray; 645 | } 646 | 647 | int sourceByteArrayLength = sourceByteArray.length; 648 | 649 | byte[] byteArray; 650 | 651 | switch (sourceByteNumber) { 652 | case 1: 653 | switch (outputByteNumber) { 654 | case 2: 655 | byteArray = new byte[sourceByteArrayLength * 2]; 656 | 657 | byte resultByte[]; 658 | 659 | for (int index = 0; index < sourceByteArrayLength; index += 1) { 660 | resultByte = CommonFunction.GetBytes((short) (sourceByteArray[index] * 256), 661 | Constant.isBigEnding); 662 | 663 | byteArray[2 * index] = resultByte[0]; 664 | byteArray[2 * index + 1] = resultByte[1]; 665 | } 666 | 667 | return byteArray; 668 | default: 669 | break; 670 | } 671 | break; 672 | case 2: 673 | switch (outputByteNumber) { 674 | case 1: 675 | int outputByteArrayLength = sourceByteArrayLength / 2; 676 | 677 | byteArray = new byte[outputByteArrayLength]; 678 | 679 | for (int index = 0; index < outputByteArrayLength; index += 1) { 680 | byteArray[index] = (byte) (CommonFunction.GetShort(sourceByteArray[2 * index], 681 | sourceByteArray[2 * index + 1], Constant.isBigEnding) / 256); 682 | } 683 | 684 | return byteArray; 685 | default: 686 | break; 687 | } 688 | break; 689 | default: 690 | break; 691 | } 692 | 693 | return sourceByteArray; 694 | } 695 | 696 | /** 697 | * 重置声道数 698 | */ 699 | public static byte[] convertChannelNumber(int sourceChannelCount, int outputChannelCount, 700 | int byteNumber, byte[] sourceByteArray) { 701 | if (sourceChannelCount == outputChannelCount) { 702 | return sourceByteArray; 703 | } 704 | 705 | switch (byteNumber) { 706 | case 1: 707 | case 2: 708 | break; 709 | default: 710 | return sourceByteArray; 711 | } 712 | 713 | int sourceByteArrayLength = sourceByteArray.length; 714 | 715 | byte[] byteArray; 716 | 717 | switch (sourceChannelCount) { 718 | case 1: 719 | switch (outputChannelCount) { 720 | case 2: 721 | byteArray = new byte[sourceByteArrayLength * 2]; 722 | 723 | byte firstByte; 724 | byte secondByte; 725 | 726 | switch (byteNumber) { 727 | case 1: 728 | for (int index = 0; index < sourceByteArrayLength; index += 1) { 729 | firstByte = sourceByteArray[index]; 730 | 731 | byteArray[2 * index] = firstByte; 732 | byteArray[2 * index + 1] = firstByte; 733 | } 734 | break; 735 | case 2: 736 | for (int index = 0; index < sourceByteArrayLength; index += 2) { 737 | firstByte = sourceByteArray[index]; 738 | secondByte = sourceByteArray[index + 1]; 739 | 740 | byteArray[2 * index] = firstByte; 741 | byteArray[2 * index + 1] = secondByte; 742 | byteArray[2 * index + 2] = firstByte; 743 | byteArray[2 * index + 3] = secondByte; 744 | } 745 | break; 746 | default: 747 | break; 748 | } 749 | 750 | return byteArray; 751 | default: 752 | break; 753 | } 754 | break; 755 | case 2: 756 | switch (outputChannelCount) { 757 | case 1: 758 | int outputByteArrayLength = sourceByteArrayLength / 2; 759 | 760 | byteArray = new byte[outputByteArrayLength]; 761 | 762 | switch (byteNumber) { 763 | case 1: 764 | for (int index = 0; index < outputByteArrayLength; index += 2) { 765 | short averageNumber = 766 | (short) ((short) sourceByteArray[2 * index] + (short) sourceByteArray[2 767 | * index + 1]); 768 | byteArray[index] = (byte) (averageNumber >> 1); 769 | } 770 | break; 771 | case 2: 772 | for (int index = 0; index < outputByteArrayLength; index += 2) { 773 | byte resultByte[] = 774 | CommonFunction.AverageShortByteArray(sourceByteArray[2 * index], 775 | sourceByteArray[2 * index + 1], sourceByteArray[2 * index + 2], 776 | sourceByteArray[2 * index + 3], Constant.isBigEnding); 777 | 778 | byteArray[index] = resultByte[0]; 779 | byteArray[index + 1] = resultByte[1]; 780 | } 781 | break; 782 | default: 783 | break; 784 | } 785 | 786 | return byteArray; 787 | default: 788 | break; 789 | } 790 | break; 791 | default: 792 | break; 793 | } 794 | 795 | return sourceByteArray; 796 | } 797 | 798 | private void notifyProgress(final DecodeOperateInterface decodeOperateInterface, final int progress){ 799 | new Handler(Looper.getMainLooper()).post(new Runnable() { 800 | @Override public void run() { 801 | if (decodeOperateInterface != null) { 802 | decodeOperateInterface.updateDecodeProgress(progress); 803 | } 804 | } 805 | }); 806 | } 807 | } 808 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/DecodeUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 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 com.audio.util.encode.CommonFunction; 10 | import com.audio.util.ssrc.SSRC; 11 | import com.audio.util.util.Constant; 12 | 13 | import java.io.BufferedOutputStream; 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.nio.ByteBuffer; 19 | 20 | public class DecodeUtil { 21 | public interface DecodeOperateInterface{ 22 | public void onLoadedAudioInfo(String audio); 23 | public void onLoadedError(String audio); 24 | } 25 | 26 | /** 27 | * 获取媒体音频文件时长 28 | * @param url 音频文件存放url可以是本地文件也可以是网络文件 29 | * @return 30 | */ 31 | public static long getAudioDuration(String url){ 32 | long start = System.currentTimeMillis(); 33 | Log.d("ddebug","getAudioDuration---start = " + start); 34 | long duration = -1; 35 | MediaExtractor extractor = new MediaExtractor(); 36 | try { 37 | extractor.setDataSource(url); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | try { 41 | extractor.setDataSource(new FileInputStream(url).getFD()); 42 | } catch (IOException ioException) { 43 | ioException.printStackTrace(); 44 | Log.e("ddebug","解码音频文件路径设置出错"); 45 | } 46 | } 47 | if(extractor.getTrackCount() <= 0){ 48 | return -1; 49 | } 50 | MediaFormat format = extractor.getTrackFormat(0); 51 | duration = format.containsKey(MediaFormat.KEY_DURATION)?format.getLong(MediaFormat.KEY_DURATION):0; 52 | duration = duration/1000;//转换为毫秒 53 | Log.d("ddebug","getAudioDuration--- = " + (System.currentTimeMillis() - start)); 54 | return duration; 55 | } 56 | 57 | /** 58 | * 将音乐文件解码 59 | * 60 | * @param musicFileUrl 源文件路径 61 | * @param decodeFileUrl 解码文件路径 62 | * @param startMicroseconds 开始时间 微秒 63 | * @param endMicroseconds 结束时间 微秒 64 | * @param decodeOperateInterface 解码过程回调 65 | */ 66 | public static boolean decodeAudio(String musicFileUrl, String decodeFileUrl, 67 | long startMicroseconds, long endMicroseconds, DecodeOperateInterface decodeOperateInterface){ 68 | //采样率 69 | int sampleRate = 0; 70 | //声道数 71 | int channelCount = 0; 72 | //时长 73 | long duration = 0; 74 | //音频文件类型 75 | String mime = null; 76 | //bit率 77 | int bitRate = 0; 78 | //采样位数 79 | int sampleBit = 0; 80 | 81 | //bit率=采样率*声道数*采样位数 82 | //由上推导得出:采样位数= bit率/(采样率*声道数) 83 | 84 | MediaExtractor mediaExtractor = new MediaExtractor(); 85 | //获取音频格式轨信息 86 | MediaFormat mediaFormat; 87 | MediaCodec mediaCodec; 88 | 89 | //给媒体信息提取器设置源音频文件路径 90 | try { 91 | mediaExtractor.setDataSource(musicFileUrl); 92 | } catch (IOException e) { 93 | e.printStackTrace(); 94 | try { 95 | mediaExtractor.setDataSource(new FileInputStream(musicFileUrl).getFD()); 96 | } catch (IOException ioException) { 97 | ioException.printStackTrace(); 98 | Log.e("ddebug","设置解码音频文件路径错误"); 99 | } 100 | } 101 | 102 | mediaFormat = mediaExtractor.getTrackFormat(0); 103 | 104 | //从音频格式轨信息中读取 采样率,声道数,时长,音频文件类型 105 | sampleRate = mediaFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100; 106 | channelCount = mediaFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1; 107 | duration = mediaFormat.containsKey(MediaFormat.KEY_DURATION) ? mediaFormat.getLong(MediaFormat.KEY_DURATION) : 0; 108 | mime = mediaFormat.containsKey(MediaFormat.KEY_MIME) ? mediaFormat.getString(MediaFormat.KEY_MIME) : ""; 109 | bitRate = mediaFormat.containsKey(MediaFormat.KEY_BIT_RATE) ? mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE) : 1; 110 | //bit率=采样率*声道数*采样位数 111 | //由上推导得出:采样位数= bit率/(采样率*声道数) 112 | sampleBit = bitRate/(sampleRate*channelCount); 113 | 114 | int pcmEncoding = mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING) ? mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING) : 2; 115 | 116 | String info = "音频信息Track info:\npath:" + musicFileUrl 117 | + " \nmime:" + mime 118 | + " \n采样率sampleRate:" + sampleRate 119 | + " \nchannels:" + channelCount 120 | + " \nduration:" + duration 121 | + " \nbitRate:" + bitRate 122 | + " \nsampleBit:" + sampleBit 123 | + " \npcmEncoding:" + pcmEncoding +"\n"; 124 | if(decodeOperateInterface != null){ 125 | decodeOperateInterface.onLoadedAudioInfo(info); 126 | } 127 | if (TextUtils.isEmpty(mime) || !mime.startsWith("audio/")) { 128 | String msg = "解码文件不是音频文件mime:" + mime; 129 | if(decodeOperateInterface != null){ 130 | decodeOperateInterface.onLoadedError(msg); 131 | } 132 | LogUtil.e(msg); 133 | return false; 134 | } 135 | 136 | if (mime.equals("audio/ffmpeg")) { 137 | mime = "audio/mpeg"; 138 | mediaFormat.setString(MediaFormat.KEY_MIME, mime); 139 | } 140 | 141 | if (duration <= 0) { 142 | LogUtil.e("音频文件duration为" + duration); 143 | return false; 144 | } 145 | Log.d("debug",info); 146 | 147 | //解码的开始时间和结束时间 148 | startMicroseconds = Math.max(startMicroseconds, 0); 149 | endMicroseconds = endMicroseconds < 0 ? duration : endMicroseconds; 150 | endMicroseconds = Math.min(endMicroseconds, duration); 151 | 152 | if (startMicroseconds >= endMicroseconds) { 153 | return false; 154 | } 155 | 156 | try { 157 | mediaCodec = MediaCodec.createDecoderByType(mime); 158 | mediaCodec.configure(mediaFormat,null,null,0); 159 | 160 | } catch (IOException e) { 161 | e.printStackTrace(); 162 | LogUtil.e("解码器configure出错"); 163 | return false; 164 | } 165 | //得到输出PCM文件的路径 166 | decodeFileUrl = decodeFileUrl.substring(0, decodeFileUrl.lastIndexOf(".")); 167 | String pcmFilePath = decodeFileUrl + ".pcm"; 168 | Log.e("ddebug","pcmFilePath = " + pcmFilePath); 169 | 170 | //后续解码操作 171 | getDecodeData(mediaExtractor, mediaCodec, pcmFilePath, sampleRate, channelCount, 172 | startMicroseconds, endMicroseconds, decodeOperateInterface); 173 | 174 | return true; 175 | } 176 | 177 | /** 178 | * 解码数据 179 | */ 180 | private static void getDecodeData(MediaExtractor mediaExtractor, MediaCodec mediaCodec, 181 | String decodeFileUrl, int sampleRate, int channelCount, final long startMicroseconds, 182 | final long endMicroseconds, final DecodeOperateInterface decodeOperateInterface) { 183 | 184 | //初始化解码状态,未解析完成 185 | boolean decodeInputEnd = false; 186 | boolean decodeOutputEnd = false; 187 | 188 | //当前读取采样数据的大小 189 | int sampleDataSize; 190 | //当前输入数据的ByteBuffer序号,当前输出数据的ByteBuffer序号 191 | int inputBufferIndex; 192 | int outputBufferIndex; 193 | //音频文件的采样位数字节数,= 采样位数/8 194 | int byteNumber; 195 | 196 | //上一次的解码操作时间,当前解码操作时间,用于通知回调接口 197 | long decodeNoticeTime = System.currentTimeMillis(); 198 | long decodeTime; 199 | 200 | //当前采样的音频时间,比如在当前音频的第40秒的时候 201 | long presentationTimeUs = 0; 202 | 203 | //定义编解码的超时时间 204 | final long timeOutUs = 100; 205 | 206 | //存储输入数据的ByteBuffer数组,输出数据的ByteBuffer数组 207 | ByteBuffer[] inputBuffers; 208 | ByteBuffer[] outputBuffers; 209 | 210 | //当前编解码器操作的 输入数据ByteBuffer 和 输出数据ByteBuffer,可以从targetBuffer中获取解码后的PCM数据 211 | ByteBuffer sourceBuffer; 212 | ByteBuffer targetBuffer; 213 | 214 | //获取输出音频的媒体格式信息 215 | MediaFormat outputFormat = mediaCodec.getOutputFormat(); 216 | 217 | MediaCodec.BufferInfo bufferInfo; 218 | 219 | byteNumber = (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) / 8; 220 | Log.d("ddebug","getDecodeData byteNumber = " + byteNumber); 221 | 222 | //开始解码操作 223 | mediaCodec.start(); 224 | 225 | //获取存储输入数据的ByteBuffer数组,输出数据的ByteBuffer数组 226 | inputBuffers = mediaCodec.getInputBuffers(); 227 | outputBuffers = mediaCodec.getOutputBuffers(); 228 | 229 | mediaExtractor.selectTrack(0); 230 | 231 | //当前解码的缓存信息,里面的有效数据在offset和offset+size之间 232 | bufferInfo = new MediaCodec.BufferInfo(); 233 | 234 | //获取解码后文件的输出流 235 | BufferedOutputStream bufferedOutputStream = 236 | FileFunction.getBufferedOutputStreamFromFile(decodeFileUrl); 237 | 238 | //开始进入循环解码操作,判断读入源音频数据是否完成,输出解码音频数据是否完成 239 | while (!decodeOutputEnd) { 240 | if (decodeInputEnd) { 241 | return; 242 | } 243 | 244 | decodeTime = System.currentTimeMillis(); 245 | 246 | //间隔1秒通知解码进度 247 | if (decodeTime - decodeNoticeTime > Constant.OneSecond) { 248 | final int decodeProgress = 249 | (int) ((presentationTimeUs - startMicroseconds) * Constant.NormalMaxProgress 250 | / endMicroseconds); 251 | 252 | if (decodeProgress > 0) { 253 | notifyProgress(decodeOperateInterface, decodeProgress); 254 | } 255 | 256 | decodeNoticeTime = decodeTime; 257 | } 258 | 259 | try { 260 | 261 | //操作解码输入数据 262 | 263 | //从队列中获取当前解码器处理输入数据的ByteBuffer序号 264 | inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs); 265 | 266 | if (inputBufferIndex >= 0) { 267 | //取得当前解码器处理输入数据的ByteBuffer 268 | sourceBuffer = inputBuffers[inputBufferIndex]; 269 | //获取当前ByteBuffer,编解码器读取了多少采样数据 270 | sampleDataSize = mediaExtractor.readSampleData(sourceBuffer, 0); 271 | 272 | //如果当前读取的采样数据<0,说明已经完成了读取操作 273 | if (sampleDataSize < 0) { 274 | decodeInputEnd = true; 275 | sampleDataSize = 0; 276 | } else { 277 | presentationTimeUs = mediaExtractor.getSampleTime(); 278 | } 279 | 280 | //然后将当前ByteBuffer重新加入到队列中交给编解码器做下一步读取操作 281 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleDataSize, presentationTimeUs, 282 | decodeInputEnd ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 283 | 284 | //前进到下一段采样数据 285 | if (!decodeInputEnd) { 286 | mediaExtractor.advance(); 287 | } 288 | 289 | } else { 290 | //LogUtil.e("inputBufferIndex" + inputBufferIndex); 291 | } 292 | 293 | //操作解码输出数据 294 | 295 | //从队列中获取当前解码器处理输出数据的ByteBuffer序号 296 | outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs); 297 | 298 | if (outputBufferIndex < 0) { 299 | //输出ByteBuffer序号<0,可能是输出缓存变化了,输出格式信息变化了 300 | switch (outputBufferIndex) { 301 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 302 | outputBuffers = mediaCodec.getOutputBuffers(); 303 | LogUtil.e( 304 | "MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED [AudioDecoder]output buffers have changed."); 305 | break; 306 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 307 | outputFormat = mediaCodec.getOutputFormat(); 308 | 309 | sampleRate = 310 | outputFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? outputFormat.getInteger( 311 | MediaFormat.KEY_SAMPLE_RATE) : sampleRate; 312 | channelCount = 313 | outputFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? outputFormat.getInteger( 314 | MediaFormat.KEY_CHANNEL_COUNT) : channelCount; 315 | byteNumber = 316 | (outputFormat.containsKey("bit-width") ? outputFormat.getInteger("bit-width") : 0) 317 | / 8; 318 | 319 | LogUtil.e( 320 | "MediaCodec.INFO_OUTPUT_FORMAT_CHANGED [AudioDecoder]output format has changed to " 321 | + mediaCodec.getOutputFormat()); 322 | break; 323 | default: 324 | //LogUtil.e("error [AudioDecoder] dequeueOutputBuffer returned " + outputBufferIndex); 325 | break; 326 | } 327 | continue; 328 | } 329 | 330 | //取得当前解码器处理输出数据的ByteBuffer 331 | targetBuffer = outputBuffers[outputBufferIndex]; 332 | 333 | byte[] sourceByteArray = new byte[bufferInfo.size]; 334 | 335 | //将解码后的targetBuffer中的数据复制到sourceByteArray中 336 | targetBuffer.get(sourceByteArray); 337 | targetBuffer.clear(); 338 | 339 | //释放当前的输出缓存 340 | mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 341 | 342 | //判断当前是否解码数据全部结束了 343 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 344 | decodeOutputEnd = true; 345 | } 346 | 347 | //sourceByteArray就是最终解码后的采样数据 348 | //接下来可以对这些数据进行采样位数,声道的转换,但这是可选的,默认是和源音频一样的声道和采样位数 349 | if (sourceByteArray.length > 0 && bufferedOutputStream != null) { 350 | if (presentationTimeUs < startMicroseconds) { 351 | continue; 352 | } 353 | 354 | //采样位数转换,按自己需要是否实现 355 | byte[] convertByteNumberByteArray = 356 | convertByteNumber(byteNumber, Constant.ExportByteNumber, sourceByteArray); 357 | 358 | //声道转换,按自己需要是否实现 359 | byte[] resultByteArray = convertChannelNumber(channelCount, Constant.ExportChannelNumber, 360 | Constant.ExportByteNumber, convertByteNumberByteArray); 361 | 362 | //将解码后的PCM数据写入到PCM文件 363 | try { 364 | bufferedOutputStream.write(resultByteArray); 365 | } catch (Exception e) { 366 | LogUtil.e("输出解压音频数据异常" + e); 367 | } 368 | } 369 | 370 | if (presentationTimeUs > endMicroseconds) { 371 | break; 372 | } 373 | } catch (Exception e) { 374 | LogUtil.e("getDecodeData异常" + e); 375 | } 376 | } 377 | 378 | if (bufferedOutputStream != null) { 379 | try { 380 | bufferedOutputStream.close(); 381 | } catch (IOException e) { 382 | LogUtil.e("关闭bufferedOutputStream异常" + e); 383 | } 384 | } 385 | 386 | //重置采样率,按自己需要是否实现 387 | if (sampleRate != Constant.ExportSampleRate) { 388 | Resample(sampleRate, decodeFileUrl); 389 | } 390 | 391 | notifyProgress(decodeOperateInterface, 100); 392 | 393 | //释放mediaCodec 和 mediaExtractor 394 | if (mediaCodec != null) { 395 | mediaCodec.stop(); 396 | mediaCodec.release(); 397 | } 398 | 399 | if (mediaExtractor != null) { 400 | mediaExtractor.release(); 401 | } 402 | } 403 | private static void notifyProgress(DecodeOperateInterface decodeOperateInterface, int decodeProgress) { 404 | Log.d("ddebug","decodeProgress = " + decodeProgress); 405 | } 406 | /** 407 | * 重置采样字节数 408 | */ 409 | public static byte[] convertByteNumber(int sourceByteNumber, int outputByteNumber, 410 | byte[] sourceByteArray) { 411 | if (sourceByteNumber == outputByteNumber) { 412 | return sourceByteArray; 413 | } 414 | 415 | int sourceByteArrayLength = sourceByteArray.length; 416 | 417 | byte[] byteArray; 418 | 419 | switch (sourceByteNumber) { 420 | case 1: 421 | switch (outputByteNumber) { 422 | case 2: 423 | byteArray = new byte[sourceByteArrayLength * 2]; 424 | 425 | byte resultByte[]; 426 | 427 | for (int index = 0; index < sourceByteArrayLength; index += 1) { 428 | resultByte = CommonFunction.GetBytes((short) (sourceByteArray[index] * 256), 429 | Constant.isBigEnding); 430 | 431 | byteArray[2 * index] = resultByte[0]; 432 | byteArray[2 * index + 1] = resultByte[1]; 433 | } 434 | 435 | return byteArray; 436 | default: 437 | break; 438 | } 439 | break; 440 | case 2: 441 | switch (outputByteNumber) { 442 | case 1: 443 | int outputByteArrayLength = sourceByteArrayLength / 2; 444 | 445 | byteArray = new byte[outputByteArrayLength]; 446 | 447 | for (int index = 0; index < outputByteArrayLength; index += 1) { 448 | byteArray[index] = (byte) (CommonFunction.GetShort(sourceByteArray[2 * index], 449 | sourceByteArray[2 * index + 1], Constant.isBigEnding) / 256); 450 | } 451 | 452 | return byteArray; 453 | default: 454 | break; 455 | } 456 | break; 457 | default: 458 | break; 459 | } 460 | 461 | return sourceByteArray; 462 | } 463 | /** 464 | * 重置采样率 465 | */ 466 | private static void Resample(int sampleRate, String decodeFileUrl) { 467 | String newDecodeFileUrl = decodeFileUrl + "new"; 468 | 469 | try { 470 | FileInputStream fileInputStream = new FileInputStream(new File(decodeFileUrl)); 471 | FileOutputStream fileOutputStream = new FileOutputStream(new File(newDecodeFileUrl)); 472 | 473 | new SSRC(fileInputStream, fileOutputStream, sampleRate, Constant.ExportSampleRate, 474 | Constant.ExportByteNumber, Constant.ExportByteNumber, 1, Integer.MAX_VALUE, 0, 0, true); 475 | 476 | fileInputStream.close(); 477 | fileOutputStream.close(); 478 | 479 | FileFunction.renameFile(newDecodeFileUrl, decodeFileUrl); 480 | } catch (IOException e) { 481 | LogUtil.e("关闭bufferedOutputStream异常" + e); 482 | } 483 | } 484 | /** 485 | * 重置声道数 486 | */ 487 | public static byte[] convertChannelNumber(int sourceChannelCount, int outputChannelCount, 488 | int byteNumber, byte[] sourceByteArray) { 489 | if (sourceChannelCount == outputChannelCount) { 490 | return sourceByteArray; 491 | } 492 | 493 | switch (byteNumber) { 494 | case 1: 495 | case 2: 496 | break; 497 | default: 498 | return sourceByteArray; 499 | } 500 | 501 | int sourceByteArrayLength = sourceByteArray.length; 502 | 503 | byte[] byteArray; 504 | 505 | switch (sourceChannelCount) { 506 | case 1: 507 | switch (outputChannelCount) { 508 | case 2: 509 | byteArray = new byte[sourceByteArrayLength * 2]; 510 | 511 | byte firstByte; 512 | byte secondByte; 513 | 514 | switch (byteNumber) { 515 | case 1: 516 | for (int index = 0; index < sourceByteArrayLength; index += 1) { 517 | firstByte = sourceByteArray[index]; 518 | 519 | byteArray[2 * index] = firstByte; 520 | byteArray[2 * index + 1] = firstByte; 521 | } 522 | break; 523 | case 2: 524 | for (int index = 0; index < sourceByteArrayLength; index += 2) { 525 | firstByte = sourceByteArray[index]; 526 | secondByte = sourceByteArray[index + 1]; 527 | 528 | byteArray[2 * index] = firstByte; 529 | byteArray[2 * index + 1] = secondByte; 530 | byteArray[2 * index + 2] = firstByte; 531 | byteArray[2 * index + 3] = secondByte; 532 | } 533 | break; 534 | default: 535 | break; 536 | } 537 | 538 | return byteArray; 539 | default: 540 | break; 541 | } 542 | break; 543 | case 2: 544 | switch (outputChannelCount) { 545 | case 1: 546 | int outputByteArrayLength = sourceByteArrayLength / 2; 547 | 548 | byteArray = new byte[outputByteArrayLength]; 549 | 550 | switch (byteNumber) { 551 | case 1: 552 | for (int index = 0; index < outputByteArrayLength; index += 2) { 553 | short averageNumber = 554 | (short) ((short) sourceByteArray[2 * index] + (short) sourceByteArray[2 555 | * index + 1]); 556 | byteArray[index] = (byte) (averageNumber >> 1); 557 | } 558 | break; 559 | case 2: 560 | for (int index = 0; index < outputByteArrayLength; index += 2) { 561 | byte resultByte[] = 562 | CommonFunction.AverageShortByteArray(sourceByteArray[2 * index], 563 | sourceByteArray[2 * index + 1], sourceByteArray[2 * index + 2], 564 | sourceByteArray[2 * index + 3], Constant.isBigEnding); 565 | 566 | byteArray[index] = resultByte[0]; 567 | byteArray[index + 1] = resultByte[1]; 568 | } 569 | break; 570 | default: 571 | break; 572 | } 573 | 574 | return byteArray; 575 | default: 576 | break; 577 | } 578 | break; 579 | default: 580 | break; 581 | } 582 | 583 | return sourceByteArray; 584 | } 585 | } 586 | 587 | 588 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/FileFunction.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.os.Environment; 4 | import android.text.TextUtils; 5 | import java.io.BufferedOutputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | 11 | public class FileFunction { 12 | public static boolean isExitsSdcard() { 13 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 14 | } 15 | 16 | public static boolean isFileExists(String path) { 17 | if (TextUtils.isEmpty(path)) { 18 | return false; 19 | } 20 | 21 | return new File(path).exists(); 22 | } 23 | 24 | private static void createDirectory(String path) { 25 | File dir = new File(path); 26 | 27 | if (!dir.exists()) { 28 | dir.mkdirs(); 29 | } 30 | } 31 | 32 | public static void saveFile(String url, String content) { 33 | saveFile(url, content, true, false); 34 | } 35 | 36 | public static void saveFile(String url, String content, boolean cover, boolean append) { 37 | FileOutputStream out = null; 38 | File file = new File(url); 39 | 40 | try { 41 | if (file.exists()) { 42 | if (cover) { 43 | file.delete(); 44 | file.createNewFile(); 45 | } 46 | } else { 47 | file.createNewFile(); 48 | } 49 | 50 | out = new FileOutputStream(file, append); 51 | out.write(content.getBytes()); 52 | out.close(); 53 | LogUtil.i("保存文件" + url + "保存文件成功"); 54 | } catch (Exception e) { 55 | LogUtil.e("保存文件" + url, e); 56 | 57 | if (out != null) { 58 | try { 59 | out.close(); 60 | } catch (IOException e1) { 61 | e1.printStackTrace(); 62 | } 63 | } 64 | } 65 | } 66 | 67 | public static void deleteFile(String path) { 68 | if (!TextUtils.isEmpty(path)) { 69 | File file = new File(path); 70 | 71 | if (file.exists()) { 72 | try { 73 | file.delete(); 74 | } catch (Exception e) { 75 | LogUtil.e("删除本地文件失败", e); 76 | } 77 | } 78 | } 79 | } 80 | 81 | public static void copyFile(String oldPath, String newPath) { 82 | try { 83 | int byteRead; 84 | 85 | File oldFile = new File(oldPath); 86 | File newFile = new File(newPath); 87 | 88 | if (oldFile.exists()) { //文件存在时 89 | if (newFile.exists()) { 90 | newFile.delete(); 91 | } 92 | 93 | newFile.createNewFile(); 94 | 95 | FileInputStream inputStream = new FileInputStream(oldPath); //读入原文件 96 | FileOutputStream outputStream = new FileOutputStream(newPath); 97 | byte[] buffer = new byte[1024]; 98 | 99 | while ((byteRead = inputStream.read(buffer)) != -1) { 100 | outputStream.write(buffer, 0, byteRead); 101 | } 102 | 103 | inputStream.close(); 104 | } 105 | } catch (Exception e) { 106 | LogUtil.e("复制单个文件操作出错", e); 107 | } 108 | } 109 | 110 | public static FileInputStream getFileInputStreamFromFile(String fileUrl) { 111 | FileInputStream fileInputStream = null; 112 | 113 | try { 114 | File file = new File(fileUrl); 115 | 116 | fileInputStream = new FileInputStream(file); 117 | } catch (Exception e) { 118 | LogUtil.e("GetBufferedInputStreamFromFile异常", e); 119 | } 120 | 121 | return fileInputStream; 122 | } 123 | 124 | public static FileOutputStream getFileOutputStreamFromFile(String fileUrl) { 125 | FileOutputStream bufferedOutputStream = null; 126 | 127 | try { 128 | File file = new File(fileUrl); 129 | 130 | if (file.exists()) { 131 | file.delete(); 132 | } 133 | 134 | file.createNewFile(); 135 | 136 | bufferedOutputStream = new FileOutputStream(file); 137 | } catch (Exception e) { 138 | LogUtil.e("GetFileOutputStreamFromFile异常", e); 139 | } 140 | 141 | return bufferedOutputStream; 142 | } 143 | 144 | public static BufferedOutputStream getBufferedOutputStreamFromFile(String fileUrl) { 145 | BufferedOutputStream bufferedOutputStream = null; 146 | 147 | try { 148 | File file = new File(fileUrl); 149 | 150 | if (file.exists()) { 151 | file.delete(); 152 | } 153 | 154 | file.createNewFile(); 155 | 156 | bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); 157 | } catch (Exception e) { 158 | LogUtil.e("GetBufferedOutputStreamFromFile异常", e); 159 | } 160 | 161 | return bufferedOutputStream; 162 | } 163 | 164 | public static void renameFile(String oldPath, String newPath) { 165 | if (!TextUtils.isEmpty(oldPath) && !TextUtils.isEmpty(newPath)) { 166 | File newFile = new File(newPath); 167 | 168 | if (newFile.exists()) { 169 | newFile.delete(); 170 | } 171 | 172 | File oldFile = new File(oldPath); 173 | 174 | if (oldFile.exists()) { 175 | try { 176 | oldFile.renameTo(new File(newPath)); 177 | } catch (Exception e) { 178 | LogUtil.e("删除本地文件失败", e); 179 | } 180 | } 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Log打印日志类 7 | */ 8 | public class LogUtil { 9 | public static boolean hideLog = false;//!Constant.Debug; 10 | private static final String TAG = "AudioEdit"; 11 | 12 | /** 13 | * @param message 14 | */ 15 | public static void v(String message) { 16 | v("", message); 17 | } 18 | 19 | /** 20 | * @param tag 21 | * @param message 22 | */ 23 | public static void v(String tag, String message) { 24 | if (hideLog) { 25 | return; 26 | } 27 | 28 | tag = TAG + "-" + tag; 29 | message = getFunctionName() + message; 30 | 31 | Log.v(tag, message); 32 | } 33 | 34 | /** 35 | * @param message 36 | */ 37 | public static void d(String message) { 38 | d("", message); 39 | } 40 | 41 | /** 42 | * @param tag 43 | * @param message 44 | */ 45 | public static void d(String tag, String message) { 46 | if (hideLog) { 47 | return; 48 | } 49 | 50 | tag = TAG + "-" + tag; 51 | message = getFunctionName() + message; 52 | 53 | Log.d(tag, message); 54 | } 55 | 56 | /** 57 | * @param message 58 | */ 59 | public static void w(String message) { 60 | w("", message); 61 | } 62 | 63 | /** 64 | * @param tag 65 | * @param message 66 | */ 67 | public static void w(String tag, String message) { 68 | if (hideLog) { 69 | return; 70 | } 71 | 72 | tag = TAG + "-" + tag; 73 | message = getFunctionName() + message; 74 | 75 | Log.w(tag, message); 76 | } 77 | 78 | /** 79 | */ 80 | public static void i() { 81 | i("", ""); 82 | } 83 | 84 | /** 85 | * @param message 86 | */ 87 | public static void i(String message) { 88 | i("", message); 89 | } 90 | 91 | /** 92 | * @param showLog 93 | * @param message 94 | */ 95 | public static void i(boolean showLog, String message) { 96 | if (!showLog) { 97 | return; 98 | } 99 | 100 | i(message); 101 | } 102 | 103 | /** 104 | * @param tag 105 | * @param message 106 | */ 107 | public static void i(String tag, String message) { 108 | if (hideLog) { 109 | return; 110 | } 111 | 112 | tag = TAG + "-" + tag; 113 | message = getFunctionName() + message; 114 | 115 | Log.i(tag, message); 116 | } 117 | 118 | /** 119 | * @param message 120 | */ 121 | public static void e(String message) { 122 | e(TAG, message); 123 | } 124 | 125 | /** 126 | * @param tag 127 | * @param message 128 | */ 129 | public static void e(String tag, String message) { 130 | e(tag, message, null); 131 | } 132 | 133 | /** 134 | * @param message 135 | * @param throwable 136 | */ 137 | public static void e(String message, Throwable throwable) { 138 | e(TAG, message, throwable); 139 | } 140 | 141 | /** 142 | * @param throwable 143 | */ 144 | public static void e(final Throwable throwable) { 145 | e(TAG, "", throwable); 146 | } 147 | 148 | /** 149 | * @param tag 150 | * @param message 151 | */ 152 | public static void e(String tag, final String message, final Throwable throwable) { 153 | if (hideLog) { 154 | return; 155 | } 156 | 157 | final String tagName = getFunctionName() + message; 158 | 159 | Log.e(tag, tagName, throwable); 160 | } 161 | 162 | /** 163 | * @param message 164 | */ 165 | public static void printStack(String message) { 166 | try { 167 | throw new Exception("打印堆栈:" + message); 168 | } catch (Exception e) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | 173 | private static String getFunctionName() { 174 | StackTraceElement[] sts = Thread.currentThread().getStackTrace(); 175 | 176 | if (sts == null) { 177 | return ""; 178 | } 179 | 180 | for (StackTraceElement st : sts) { 181 | if (st.isNativeMethod()) { 182 | continue; 183 | } 184 | 185 | if (st.getClassName().equals(Thread.class.getName())) { 186 | continue; 187 | } 188 | 189 | if (st.getClassName().equals(LogUtil.class.getName())) { 190 | continue; 191 | } 192 | 193 | return "[" 194 | + Thread.currentThread().getId() 195 | + ": " 196 | + st.getFileName() 197 | + " : " 198 | + st.getLineNumber() 199 | + " : " 200 | + st.getMethodName() 201 | + "]---"; 202 | } 203 | 204 | return ""; 205 | } 206 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util 2 | 3 | import android.Manifest 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.os.Bundle 7 | import android.os.Environment 8 | import android.util.Log 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.app.ActivityCompat 11 | import androidx.databinding.DataBindingUtil 12 | import com.audio.util.databinding.ActivityMainBinding 13 | 14 | class MainActivity : AppCompatActivity() { 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) 18 | binding.act = this 19 | 20 | checkPermission() 21 | val downloadCache = Environment.getDownloadCacheDirectory().path + "===" + Environment.getDownloadCacheDirectory().absolutePath//App.getInstance().getCacheDir().getPath(); 22 | 23 | //Log.d("ddebug","downloadCache = ${downloadCache} --- externalCacheDir.path =" + externalCacheDir?.path + "---" + externalCacheDir?.absolutePath ) 24 | } 25 | 26 | private fun checkPermission() { 27 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 28 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 10001) 29 | } 30 | } 31 | 32 | fun decode(){ 33 | startActivity(Intent(this, DecodeActivity::class.java)) 34 | } 35 | fun record(){ 36 | startActivity(Intent(this, RecordActivity::class.java)) 37 | } 38 | fun record2(){ 39 | startActivity(Intent(this, Record2Activity::class.java)) 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/MixAudioUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import com.audio.util.callback.DecodeOperateInterface; 7 | import com.audio.util.util.Constant; 8 | import com.audio.util.util.FileUtils; 9 | 10 | import java.io.File; 11 | 12 | public class MixAudioUtil { 13 | /** 14 | * 音频合成回调接口 15 | */ 16 | public interface MixAudioCallback{ 17 | public void onFinish(String msg); 18 | public void onError(String msg); 19 | } 20 | 21 | /** 22 | * 音频合成功能 23 | * @param path1 音频文件输入路径 24 | * @param path2 音频文件输入路径 25 | * @param outPath 合成后的结果文件路径 26 | * @param progress1 音量比值 27 | * @param progress2 音量比值 28 | */ 29 | public static void mixAudio(String path1, String path2, String outPath,float progress1, float progress2){ 30 | mixAudio(path1,path2,outPath,progress1,progress2,null); 31 | } 32 | 33 | /** 34 | * 音频合成功能 35 | * @param path1 音频文件输入路径 36 | * @param path2 音频文件输入路径 37 | * @param outPath 合成后的结果文件路径 38 | * @param progress1 音量比值 39 | * @param progress2 音量比值 40 | * @param callback 音频合成功能回调 41 | */ 42 | public static void mixAudio(String path1, String path2, String outPath, float progress1, float progress2, MixAudioCallback callback){ 43 | String fileName1 = new File(path1).getName(); 44 | fileName1 = fileName1.substring(0, fileName1.lastIndexOf('.')) + Constant.SUFFIX_WAV; 45 | String fileName2 = new File(path2).getName(); 46 | fileName2 = fileName2.substring(0, fileName2.lastIndexOf('.')) + Constant.SUFFIX_WAV; 47 | 48 | String destPath1 = FileUtils.getAudioEditStorageDirectory() + File.separator + fileName1; 49 | String destPath2 = FileUtils.getAudioEditStorageDirectory() + File.separator + fileName2; 50 | 51 | decodeAudio(path1, destPath1); 52 | decodeAudio(path2, destPath2); 53 | 54 | if(!FileUtils.checkFileExist(destPath1)){ 55 | String msg = "解码失败" + destPath1; 56 | //ToastUtil.showToast("解码失败" + destPath1); 57 | if(callback != null){ 58 | callback.onError(msg); 59 | } 60 | Log.d("ddebug",msg); 61 | return; 62 | } 63 | if(!FileUtils.checkFileExist(destPath2)){ 64 | String msg = "解码失败" + destPath2; 65 | //ToastUtil.showToast("解码失败" + destPath2); 66 | if(callback != null){ 67 | callback.onError(msg); 68 | } 69 | Log.d("ddebug",msg); 70 | return; 71 | } 72 | 73 | Audio audio1 = getAudioFromPath(destPath1); 74 | Audio audio2 = getAudioFromPath(destPath2); 75 | Audio outAudio = new Audio(); 76 | 77 | //String outPath = new File(new File(destPath1).getParentFile(), "out.wav").getAbsolutePath(); 78 | outAudio.setPath(outPath); 79 | Log.d("ddebug","outPath = " + outPath); 80 | 81 | if(audio1 != null && audio2 != null){ 82 | AudioEditUtil.mixAudioWithSameBaseSrcAudio(audio1, audio2, outAudio, 0, progress1, progress2); 83 | } 84 | 85 | String msg = "合成完成"; 86 | if(callback != null){ 87 | callback.onFinish(msg); 88 | } 89 | //EventBus.getDefault().post(new AudioMsg(AudioTaskCreator.ACTION_AUDIO_MIX, outAudio.getPath(), msg)); 90 | Log.d("ddebug",msg); 91 | } 92 | private static void decodeAudio(String path, String destPath){ 93 | final File file = new File(path); 94 | 95 | if(FileUtils.checkFileExist(destPath)){ 96 | FileUtils.deleteFile(new File(destPath)); 97 | } 98 | 99 | FileUtils.confirmFolderExist(new File(destPath).getParent()); 100 | 101 | DecodeEngine.getInstance().convertMusicFileToWaveFile(path, destPath, new DecodeOperateInterface() { 102 | @Override public void updateDecodeProgress(int decodeProgress) { 103 | String msg = String.format("解码文件:%s,进度:%d", file.getName(), decodeProgress) + "%"; 104 | //EventBus.getDefault().post(new AudioMsg(AudioTaskCreator.ACTION_AUDIO_MIX, msg)); 105 | } 106 | 107 | @Override public void decodeSuccess() { 108 | 109 | } 110 | 111 | @Override public void decodeFail() { 112 | 113 | } 114 | }); 115 | } 116 | /** 117 | * 获取根据解码后的文件得到audio数据 118 | * @param path 119 | * @return 120 | */ 121 | private static Audio getAudioFromPath(String path){ 122 | if(!FileUtils.checkFileExist(path)){ 123 | return null; 124 | } 125 | 126 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { 127 | try { 128 | Audio audio = Audio.createAudioFromFile(new File(path)); 129 | return audio; 130 | } catch (Exception e) { 131 | e.printStackTrace(); 132 | } 133 | } 134 | 135 | return null; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/MultiAudioMixer.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public abstract class MultiAudioMixer { 11 | 12 | private OnAudioMixListener mOnAudioMixListener; 13 | 14 | /** 15 | * 创建默认的合成器 16 | * @return 17 | */ 18 | public static MultiAudioMixer createDefaultAudioMixer(){ 19 | return createAddAudioMixer(); 20 | } 21 | 22 | /** 23 | * 创建叠加合成器 24 | * @return 25 | */ 26 | public static MultiAudioMixer createAddAudioMixer(){ 27 | return new AddAudioMixer(); 28 | } 29 | 30 | /** 31 | * 创建平均值合成器 32 | * @return 33 | */ 34 | public static MultiAudioMixer createAverageAudioMixer(){ 35 | return new AverageAudioMixer(); 36 | } 37 | 38 | /** 39 | * 创建权值合成器 40 | * @param weights 41 | * @return 42 | */ 43 | public static MultiAudioMixer createWeightAudioMixer(float[] weights){ 44 | return new WeightAudioMixer(weights); 45 | } 46 | 47 | /** 48 | * 设置合成监听 49 | * @param l 50 | */ 51 | public void setOnAudioMixListener(OnAudioMixListener l){ 52 | this.mOnAudioMixListener = l; 53 | } 54 | 55 | 56 | /** 57 | * 合成音频 58 | * 59 | * @param rawAudioFiles 合成音频的列表 60 | */ 61 | public void mixAudios(String[] rawAudioFiles){ 62 | 63 | final int fileSize = rawAudioFiles.length; 64 | 65 | FileInputStream[] audioFileStreams = new FileInputStream[fileSize]; 66 | 67 | FileInputStream inputStream; 68 | byte[][] allAudioBytes = new byte[fileSize][]; 69 | boolean[] streamDoneArray = new boolean[fileSize]; 70 | final int bufferSize = 1024; 71 | byte[] buffer = new byte[bufferSize]; 72 | int offset; 73 | 74 | try { 75 | 76 | for (int fileIndex = 0; fileIndex < fileSize; ++fileIndex) { 77 | audioFileStreams[fileIndex] = new FileInputStream(rawAudioFiles[fileIndex]); 78 | } 79 | 80 | while(true){ 81 | 82 | for(int streamIndex = 0 ; streamIndex < fileSize ; ++streamIndex){ 83 | 84 | inputStream = audioFileStreams[streamIndex]; 85 | if(!streamDoneArray[streamIndex] && (offset = inputStream.read(buffer)) != -1){ 86 | allAudioBytes[streamIndex] = Arrays.copyOf(buffer,buffer.length); 87 | }else{ 88 | streamDoneArray[streamIndex] = true; 89 | allAudioBytes[streamIndex] = new byte[bufferSize]; 90 | } 91 | } 92 | 93 | byte[] mixBytes = mixRawAudioBytes(allAudioBytes); 94 | if(mixBytes != null && mOnAudioMixListener != null){ 95 | mOnAudioMixListener.onMixing(mixBytes); 96 | } 97 | 98 | boolean done = true; 99 | for(boolean streamEnd : streamDoneArray){ 100 | if(!streamEnd){ 101 | done = false; 102 | } 103 | } 104 | 105 | if(done){ 106 | if(mOnAudioMixListener != null) 107 | mOnAudioMixListener.onMixComplete(); 108 | break; 109 | } 110 | } 111 | 112 | } catch (IOException e) { 113 | e.printStackTrace(); 114 | if(mOnAudioMixListener != null) 115 | mOnAudioMixListener.onMixError(1); 116 | }finally{ 117 | try { 118 | for(FileInputStream in : audioFileStreams){ 119 | if(in != null) 120 | in.close(); 121 | } 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | } 127 | 128 | /** 129 | * 合成音频, 可设置音频开始播放时间 130 | * 131 | * @param infoList 待合成的音频信息列表 132 | */ 133 | public void mixAudios(List infoList){ 134 | 135 | if(infoList == null || infoList.size() <= 0) return; 136 | 137 | final int fileSize = infoList.size(); 138 | 139 | FileInputStream[] audioFileStreams = new FileInputStream[fileSize]; 140 | 141 | FileInputStream inputStream; 142 | byte[][] allAudioBytes = new byte[fileSize][]; 143 | boolean[] streamDoneArray = new boolean[fileSize]; 144 | final int bufferSize = 1024; 145 | byte[] buffer = new byte[bufferSize]; 146 | int offset; 147 | 148 | int[] audioOffset = new int[fileSize]; 149 | for(int i=0; i= bufferSize){ 168 | 169 | //填充空白数据 170 | allAudioBytes[streamIndex] = new byte[bufferSize]; 171 | 172 | audioOffset[streamIndex] = curOffset - bufferSize; 173 | 174 | continue; 175 | 176 | }else if(curOffset > 0 && curOffset < bufferSize){ 177 | 178 | //填充空白数据和读取的音频数据 179 | byte[] data = new byte[bufferSize]; 180 | 181 | byte[] dataChild = new byte[bufferSize - curOffset]; 182 | inputStream.read(dataChild); 183 | 184 | System.arraycopy(dataChild, 0, data, curOffset, dataChild.length); 185 | 186 | allAudioBytes[streamIndex] = data; 187 | 188 | audioOffset[streamIndex] = 0; 189 | 190 | continue; 191 | 192 | } 193 | 194 | //处理文件流数据 195 | if(!streamDoneArray[streamIndex] && (offset = inputStream.read(buffer)) != -1){ 196 | //填充音频数据 197 | allAudioBytes[streamIndex] = Arrays.copyOf(buffer,buffer.length); 198 | }else{ 199 | //填充空白数据 200 | streamDoneArray[streamIndex] = true; 201 | allAudioBytes[streamIndex] = new byte[bufferSize]; 202 | } 203 | } 204 | 205 | //合成数据 206 | byte[] mixBytes = mixRawAudioBytes(allAudioBytes); 207 | 208 | if(mixBytes != null && mOnAudioMixListener != null){ 209 | mOnAudioMixListener.onMixing(mixBytes); 210 | } 211 | 212 | boolean done = true; 213 | for(boolean streamEnd : streamDoneArray){ 214 | if(!streamEnd){ 215 | done = false; 216 | } 217 | } 218 | 219 | if(done){ 220 | //全部合成完成 221 | if(mOnAudioMixListener != null) 222 | mOnAudioMixListener.onMixComplete(); 223 | break; 224 | } 225 | } 226 | 227 | } catch (Exception e) { 228 | e.printStackTrace(); 229 | 230 | if(mOnAudioMixListener != null){ 231 | mOnAudioMixListener.onMixError(1); 232 | } 233 | 234 | }finally{ 235 | try { 236 | for(FileInputStream in : audioFileStreams){ 237 | if(in != null) 238 | in.close(); 239 | } 240 | } catch (IOException e) { 241 | e.printStackTrace(); 242 | } 243 | } 244 | } 245 | 246 | /** 247 | * 合成音频数据 248 | * @param data 249 | * @return 250 | */ 251 | public abstract byte[] mixRawAudioBytes(byte[][] data); 252 | 253 | public interface OnAudioMixListener{ 254 | /** 255 | * 合成进行 256 | * @param mixBytes 257 | * @throws IOException 258 | */ 259 | void onMixing(byte[] mixBytes) throws IOException; 260 | 261 | /** 262 | * 合成错误 263 | * @param errorCode 264 | */ 265 | void onMixError(int errorCode); 266 | 267 | /** 268 | * 合成完成 269 | */ 270 | void onMixComplete(); 271 | } 272 | 273 | /** 274 | * 叠加合成器 275 | * @author Darcy 276 | */ 277 | private static class AddAudioMixer extends MultiAudioMixer{ 278 | 279 | @Override 280 | public byte[] mixRawAudioBytes(byte[][] bMulRoadAudioes) { 281 | 282 | if (bMulRoadAudioes == null || bMulRoadAudioes.length == 0) 283 | return null; 284 | 285 | byte[] realMixAudio = bMulRoadAudioes[0]; 286 | 287 | if(bMulRoadAudioes.length == 1) 288 | return realMixAudio; 289 | 290 | for(int rw = 0 ; rw < bMulRoadAudioes.length ; ++rw){ 291 | if(bMulRoadAudioes[rw].length != realMixAudio.length){ 292 | Log.e("app", "column of the road of audio + " + rw +" is diffrent."); 293 | return null; 294 | } 295 | } 296 | 297 | //row 代表参与合成的音频数量 298 | //column 代表一段音频的采样点数,这里所有参与合成的音频的采样点数都是相同的 299 | int row = bMulRoadAudioes.length; 300 | int coloum = realMixAudio.length / 2; 301 | short[][] sMulRoadAudioes = new short[row][coloum]; 302 | 303 | //PCM音频16位的存储是大端存储方式,即低位在前,高位在后,例如(X1Y1, X2Y2, X3Y3)数据,它代表的采样点数值就是((Y1 * 256 + X1), (Y2 * 256 + X2), (Y3 * 256 + X3)) 304 | for (int r = 0; r < row; ++r) { 305 | for (int c = 0; c < coloum; ++c) { 306 | sMulRoadAudioes[r][c] = (short) ((bMulRoadAudioes[r][c * 2] & 0xff) | (bMulRoadAudioes[r][c * 2 + 1] & 0xff) << 8); 307 | } 308 | } 309 | 310 | short[] sMixAudio = new short[coloum]; 311 | int mixVal; 312 | int sr = 0; 313 | for (int sc = 0; sc < coloum; ++sc) { 314 | mixVal = 0; 315 | sr = 0; 316 | //这里采取累加法 317 | for (; sr < row; ++sr) { 318 | mixVal += sMulRoadAudioes[sr][sc]; 319 | } 320 | //最终值不能大于short最大值,因此可能出现溢出 321 | sMixAudio[sc] = (short) (mixVal); 322 | } 323 | 324 | //short值转为大端存储的双字节序列 325 | for (sr = 0; sr < coloum; ++sr) { 326 | realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF); 327 | realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8); 328 | } 329 | 330 | return realMixAudio; 331 | } 332 | 333 | } 334 | 335 | /** 336 | * 求平均值合成器 337 | * @author Darcy 338 | */ 339 | private static class AverageAudioMixer extends MultiAudioMixer{ 340 | 341 | @Override 342 | public byte[] mixRawAudioBytes(byte[][] bMulRoadAudioes) { 343 | 344 | if (bMulRoadAudioes == null || bMulRoadAudioes.length == 0) 345 | return null; 346 | 347 | byte[] realMixAudio = bMulRoadAudioes[0]; 348 | 349 | if(bMulRoadAudioes.length == 1) 350 | return realMixAudio; 351 | 352 | for(int rw = 0 ; rw < bMulRoadAudioes.length ; ++rw){ 353 | if(bMulRoadAudioes[rw].length != realMixAudio.length){ 354 | Log.e("app", "column of the road of audio + " + rw +" is diffrent."); 355 | return null; 356 | } 357 | } 358 | 359 | int row = bMulRoadAudioes.length; 360 | int coloum = realMixAudio.length / 2; 361 | short[][] sMulRoadAudioes = new short[row][coloum]; 362 | 363 | for (int r = 0; r < row; ++r) { 364 | for (int c = 0; c < coloum; ++c) { 365 | sMulRoadAudioes[r][c] = (short) ((bMulRoadAudioes[r][c * 2] & 0xff) | (bMulRoadAudioes[r][c * 2 + 1] & 0xff) << 8); 366 | } 367 | } 368 | 369 | short[] sMixAudio = new short[coloum]; 370 | int mixVal; 371 | int sr = 0; 372 | for (int sc = 0; sc < coloum; ++sc) { 373 | mixVal = 0; 374 | sr = 0; 375 | for (; sr < row; ++sr) { 376 | mixVal += sMulRoadAudioes[sr][sc]; 377 | } 378 | sMixAudio[sc] = (short) (mixVal / row); 379 | } 380 | 381 | for (sr = 0; sr < coloum; ++sr) { 382 | realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF); 383 | realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8); 384 | } 385 | 386 | return realMixAudio; 387 | } 388 | 389 | } 390 | 391 | /** 392 | * 权重求值合成器 393 | * @author Darcy 394 | */ 395 | private static class WeightAudioMixer extends MultiAudioMixer{ 396 | private float[] weights; 397 | 398 | public WeightAudioMixer(float[] weights){ 399 | this.weights = weights; 400 | } 401 | 402 | @Override 403 | public byte[] mixRawAudioBytes(byte[][] bMulRoadAudioes) { 404 | 405 | if (bMulRoadAudioes == null || bMulRoadAudioes.length == 0){ 406 | return null; 407 | } 408 | 409 | if(weights == null || weights.length != bMulRoadAudioes.length){ 410 | return null; 411 | } 412 | 413 | byte[] realMixAudio = bMulRoadAudioes[0]; 414 | 415 | if(bMulRoadAudioes.length == 1) 416 | return realMixAudio; 417 | 418 | for(int rw = 0 ; rw < bMulRoadAudioes.length ; ++rw){ 419 | if(bMulRoadAudioes[rw].length != realMixAudio.length){ 420 | Log.e("app", "column of the road of audio + " + rw +" is diffrent."); 421 | return null; 422 | } 423 | } 424 | 425 | int row = bMulRoadAudioes.length; 426 | int coloum = realMixAudio.length / 2; 427 | short[][] sMulRoadAudioes = new short[row][coloum]; 428 | 429 | for (int r = 0; r < row; ++r) { 430 | for (int c = 0; c < coloum; ++c) { 431 | sMulRoadAudioes[r][c] = (short) ((bMulRoadAudioes[r][c * 2] & 0xff) | (bMulRoadAudioes[r][c * 2 + 1] & 0xff) << 8); 432 | } 433 | } 434 | 435 | short[] sMixAudio = new short[coloum]; 436 | int mixVal; 437 | int sr = 0; 438 | for (int sc = 0; sc < coloum; ++sc) { 439 | mixVal = 0; 440 | sr = 0; 441 | for (; sr < row; ++sr) { 442 | mixVal += sMulRoadAudioes[sr][sc] * weights[sr]; 443 | } 444 | sMixAudio[sc] = (short) (mixVal); 445 | } 446 | 447 | for (sr = 0; sr < coloum; ++sr) { 448 | realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF); 449 | realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8); 450 | } 451 | 452 | return realMixAudio; 453 | } 454 | 455 | } 456 | 457 | } 458 | 459 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/Record2Activity.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.View 7 | import com.audio.util.record.AudioRecordUtil 8 | import com.audio.util.record.RecordCallback 9 | import java.io.File 10 | 11 | class Record2Activity : AppCompatActivity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_record2) 15 | } 16 | fun start(v: View){ 17 | val path = externalCacheDir?.absolutePath.toString() + File.separator + "hello/test3.wav" 18 | AudioUtilHelper.startRecord(this,path,object :RecordCallback{ 19 | override fun onRecordError(errorMsg: String) { 20 | Log.d("ddebug","Record2Activity onRecordError $errorMsg") 21 | } 22 | 23 | override fun onRecordStatusChanged(isInRecording: Boolean) { 24 | Log.d("ddebug","Record2Activity onRecordStatusChanged $isInRecording") 25 | } 26 | 27 | }) 28 | } 29 | fun pause(v: View){ 30 | AudioUtilHelper.pause() 31 | } 32 | fun finish(v: View){ 33 | AudioUtilHelper.finishRecord() 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/RecordActivity.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.media.AudioFormat 6 | import android.media.AudioRecord 7 | import android.media.MediaRecorder 8 | import android.os.* 9 | import android.util.Log 10 | import android.view.View 11 | import androidx.appcompat.app.AppCompatActivity 12 | import androidx.core.app.ActivityCompat 13 | import androidx.core.content.ContextCompat 14 | import com.audio.util.record.AudioRecordUtil 15 | import com.audio.util.record.RecordCallback 16 | import kotlinx.android.synthetic.main.activity_record.* 17 | import java.io.File 18 | import java.io.FileNotFoundException 19 | import java.io.FileOutputStream 20 | import java.io.IOException 21 | 22 | 23 | class RecordActivity : AppCompatActivity() { 24 | val MY_PERMISSIONS_REQUEST = 132 25 | val TAG = "record" 26 | /** 27 | * 需要申请的运行时权限 28 | */ 29 | private val permissions = arrayOf( 30 | Manifest.permission.RECORD_AUDIO, 31 | Manifest.permission.WRITE_EXTERNAL_STORAGE 32 | ) 33 | 34 | var audioRecord:AudioRecord? = null; 35 | lateinit var handlerThread:HandlerThread 36 | lateinit var workHandler:Handler 37 | 38 | lateinit var audioPcmPath:String //= getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath.toString() + File.separator + "hello/test.pcm" 39 | lateinit var audioWavPath:String //= getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath.toString() + File.separator + "hello/test.wav" 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | setContentView(R.layout.activity_record) 43 | init() 44 | checkPermissions() 45 | } 46 | 47 | private fun init() { 48 | val minBufferSize = 49 | AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT) 50 | //MediaRecorder.AudioSource.VOICE_COMMUNICATION 51 | audioRecord = AudioRecord( 52 | MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, 53 | CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize 54 | ) 55 | 56 | handlerThread = HandlerThread("Record"); 57 | handlerThread.start(); 58 | workHandler = Handler(handlerThread.getLooper()); 59 | 60 | audioPcmPath = externalCacheDir?.absolutePath.toString() + File.separator + "hello/test.pcm"//getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath.toString() + File.separator + "hello/test.pcm" 61 | audioWavPath = externalCacheDir?.absolutePath.toString() + File.separator + "hello/test.wav" 62 | } 63 | 64 | val mPermissionList = arrayListOf() 65 | /** 66 | * 检查权限 67 | */ 68 | private fun checkPermissions() { 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 70 | for (i in 0 until permissions.size) { 71 | if (ContextCompat.checkSelfPermission(this, permissions[i]) != 72 | PackageManager.PERMISSION_GRANTED 73 | ) { 74 | 75 | mPermissionList.add(permissions[i]) 76 | } 77 | } 78 | if (!mPermissionList.isEmpty()) { 79 | val permissions: Array = 80 | mPermissionList.toArray(arrayOfNulls(mPermissionList.size)) 81 | ActivityCompat.requestPermissions(this, permissions, MY_PERMISSIONS_REQUEST) 82 | } 83 | } 84 | } 85 | override fun onRequestPermissionsResult( 86 | requestCode: Int, 87 | permissions: Array, 88 | grantResults: IntArray 89 | ) { 90 | if (requestCode === MY_PERMISSIONS_REQUEST) { 91 | for (i in 0 until grantResults.size) { 92 | if (grantResults[i] !== PackageManager.PERMISSION_GRANTED) { 93 | Log.i(TAG, permissions[i] + " 权限被用户禁止!") 94 | } 95 | } 96 | } 97 | } 98 | 99 | fun start(v: View){ 100 | startRecord() 101 | } 102 | fun stop(v:View){ 103 | stopRecord(); 104 | } 105 | 106 | fun stop2(v:View){ 107 | stopRecord2(); 108 | } 109 | 110 | fun start2(v: View){ 111 | val path = externalCacheDir?.absolutePath.toString() + File.separator + "hello/test2.wav" 112 | AudioRecordUtil.startRecord(this,path,object : RecordCallback { 113 | override fun onRecordError(errorMsg: String) { 114 | Log.d("ddebug","onRecordError = " + errorMsg) 115 | } 116 | 117 | override fun onRecordStatusChanged(isInRecording: Boolean) { 118 | Log.d("ddebug","onRecordStatusChanged isInRecording = $isInRecording") 119 | } 120 | 121 | }); 122 | } 123 | 124 | fun pause(v: View){ 125 | AudioRecordUtil.pause() 126 | } 127 | 128 | fun finishRecord2(v: View){ 129 | AudioRecordUtil.finishRecord() 130 | } 131 | /** 132 | * 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。 133 | */ 134 | val SAMPLE_RATE_INHZ = 44100 135 | 136 | /** 137 | * 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 138 | */ 139 | val CHANNEL_CONFIG: Int = AudioFormat.CHANNEL_IN_STEREO 140 | 141 | /** 142 | * 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. 143 | */ 144 | val AUDIO_FORMAT: Int = AudioFormat.ENCODING_PCM_16BIT 145 | 146 | var isRecording = false 147 | 148 | /** 149 | * 开始录音 150 | */ 151 | private fun startRecord() { 152 | // 获取最小录音缓存大小, 153 | val minBufferSize = 154 | AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT) 155 | 156 | if(audioRecord == null){ 157 | //MediaRecorder.AudioSource.VOICE_COMMUNICATION 158 | audioRecord = AudioRecord( 159 | MediaRecorder.AudioSource.MIC, SAMPLE_RATE_INHZ, 160 | CHANNEL_CONFIG, AUDIO_FORMAT, minBufferSize 161 | ) 162 | } 163 | 164 | // 初始化缓存 165 | val data = ByteArray(minBufferSize) 166 | val file = File(audioPcmPath) 167 | Log.i(TAG, "path:" + file.getAbsolutePath()) 168 | tv.append("\n"+"文件位置:" + file.getAbsolutePath());//tvRecordFilePosition.setText("文件位置:" + file.getAbsolutePath()) 169 | if(!file.exists()){ 170 | file.parentFile.mkdirs() 171 | 172 | if (!file.createNewFile()) { 173 | val msg = "文件未能创建成功:" + file.getAbsolutePath() 174 | Log.d(TAG, msg) 175 | tv.append("\n" + msg) 176 | return 177 | } 178 | } 179 | 180 | // 开始录音 181 | audioRecord?.startRecording() 182 | isRecording = true 183 | // 创建数据流,将缓存导入数据流 184 | workHandler.post(Runnable { 185 | var fos: FileOutputStream? = null 186 | try { 187 | fos = FileOutputStream(file,true) 188 | } catch (e: FileNotFoundException) { 189 | e.printStackTrace() 190 | 191 | val msg = "文件未找到" 192 | Log.e(TAG,msg) 193 | tv.append("\n" + msg) 194 | } 195 | if (fos == null) return@Runnable 196 | while (isRecording) { 197 | val length: Int = audioRecord!!.read(data, 0, minBufferSize) 198 | if (AudioRecord.ERROR_INVALID_OPERATION != length) { 199 | try { 200 | fos.write(data, 0, length) 201 | } catch (e: IOException) { 202 | e.printStackTrace() 203 | } 204 | } 205 | } 206 | try { 207 | // 关闭数据流 208 | fos.close() 209 | } catch (e: IOException) { 210 | e.printStackTrace() 211 | } 212 | }) 213 | } 214 | 215 | /** 216 | * 停止录音 217 | */ 218 | private fun stopRecord() { 219 | isRecording = false 220 | audioRecord?.let { 221 | it.stop() 222 | it.release() 223 | audioRecord = null 224 | //ConvertUtil.convertPcm2Wav(audioPcmPath, audioWavPath) 225 | //File(audioPcmPath).delete() 226 | } 227 | } 228 | 229 | /** 230 | * 完成录制 231 | */ 232 | fun finishRecord(v:View){ 233 | ConvertUtil.convertPcm2Wav(audioPcmPath, audioWavPath) 234 | File(audioPcmPath).delete() 235 | } 236 | 237 | /** 238 | * 停止录音 239 | */ 240 | private fun stopRecord2() { 241 | isRecording = false 242 | audioRecord?.let { 243 | it.stop() 244 | it.release() 245 | audioRecord = null 246 | ConvertUtil.convertPcm2Wav2(audioPcmPath, audioWavPath) 247 | File(audioPcmPath).delete() 248 | } 249 | } 250 | fun convert(v:View){ 251 | convert2Wav() 252 | } 253 | private fun convert2Wav(){ 254 | //var path = getExternalFilesDir(Environment.DIRECTORY_MUSIC).absolutePath.toString() + File.separator + "hello/test.pcm" 255 | //var pathTaret = Environment.getExternalStorageDirectory().absolutePath + File.separator + "temp/姑娘我爱你convert${System.currentTimeMillis()}.wav" 256 | ConvertUtil.convertPcm2Wav(audioPcmPath, audioWavPath) 257 | } 258 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/WavMergeUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util; 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.IOException; 9 | import java.io.RandomAccessFile; 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.util.List; 13 | 14 | public class WavMergeUtil { 15 | /** 16 | * 拼接wav音频的文件 17 | * @param inputs 18 | * @param output 19 | * @throws IOException 20 | */ 21 | public static void mergeWav(List inputs, File output) throws IOException { 22 | if (inputs.size() < 1) { 23 | return; 24 | } 25 | FileInputStream fis = new FileInputStream(inputs.get(0)); 26 | FileOutputStream fos = new FileOutputStream(output); 27 | byte[] buffer = new byte[2048]; 28 | int total = 0; 29 | int count; 30 | while ((count = fis.read(buffer)) > -1) { 31 | fos.write(buffer, 0, count); 32 | total += count; 33 | } 34 | fis.close(); 35 | for (int i = 1; i < inputs.size(); i++) { 36 | File file = inputs.get(i); 37 | Header header = resolveHeader(file); 38 | FileInputStream dataInputStream = header.dataInputStream; 39 | while ((count = dataInputStream.read(buffer)) > -1) { 40 | fos.write(buffer, 0, count); 41 | total += count; 42 | } 43 | dataInputStream.close(); 44 | } 45 | fos.flush(); 46 | fos.close(); 47 | Header outputHeader = resolveHeader(output); 48 | outputHeader.dataInputStream.close(); 49 | RandomAccessFile res = new RandomAccessFile(output, "rw"); 50 | res.seek(4); 51 | byte[] fileLen = intToByteArray(total + outputHeader.dataOffset - 8); 52 | res.write(fileLen, 0, 4); 53 | res.seek(outputHeader.dataSizeOffset); 54 | byte[] dataLen = intToByteArray(total); 55 | res.write(dataLen, 0, 4); 56 | res.close(); 57 | Log.d("ddebug","merge 文件合并完毕"); 58 | } 59 | 60 | /** 61 | * 解析头部,并获得文件指针指向数据开始位置的InputStreram,记得使用后需要关闭 62 | */ 63 | private static Header resolveHeader(File wavFile) throws IOException { 64 | FileInputStream fis = new FileInputStream(wavFile); 65 | byte[] byte4 = new byte[4]; 66 | byte[] buffer = new byte[2048]; 67 | int readCount = 0; 68 | Header header = new Header(); 69 | fis.read(byte4);//RIFF 70 | fis.read(byte4); 71 | readCount += 8; 72 | header.fileSizeOffset = 4; 73 | header.fileSize = byteArrayToInt(byte4); 74 | fis.read(byte4);//WAVE 75 | fis.read(byte4);//fmt 76 | fis.read(byte4); 77 | readCount += 12; 78 | int fmtLen = byteArrayToInt(byte4); 79 | fis.read(buffer, 0, fmtLen); 80 | readCount += fmtLen; 81 | fis.read(byte4);//data or fact 82 | readCount += 4; 83 | if (isFmt(byte4, 0)) {//包含fmt段 84 | fis.read(byte4); 85 | int factLen = byteArrayToInt(byte4); 86 | fis.read(buffer, 0, factLen); 87 | fis.read(byte4);//data 88 | readCount += 8 + factLen; 89 | } 90 | fis.read(byte4);// data size 91 | int dataLen = byteArrayToInt(byte4); 92 | header.dataSize = dataLen; 93 | header.dataSizeOffset = readCount; 94 | readCount += 4; 95 | header.dataOffset = readCount; 96 | header.dataInputStream = fis; 97 | return header; 98 | } 99 | 100 | private static boolean isRiff(byte[] bytes, int start) { 101 | if (bytes[start + 0] == 'R' && bytes[start + 1] == 'I' && bytes[start + 2] == 'F' && bytes[start + 3] == 'F') { 102 | return true; 103 | } else { 104 | return false; 105 | } 106 | } 107 | 108 | private static boolean isFmt(byte[] bytes, int start) { 109 | if (bytes[start + 0] == 'f' && bytes[start + 1] == 'm' && bytes[start + 2] == 't' && bytes[start + 3] == ' ') { 110 | return true; 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | private static boolean isData(byte[] bytes, int start) { 117 | if (bytes[start + 0] == 'd' && bytes[start + 1] == 'a' && bytes[start + 2] == 't' && bytes[start + 3] == 'a') { 118 | return true; 119 | } else { 120 | return false; 121 | } 122 | } 123 | 124 | /** 125 | * 将int转化为byte[] 126 | */ 127 | private static byte[] intToByteArray(int data) { 128 | return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data).array(); 129 | } 130 | 131 | /** 132 | * 将short转化为byte[] 133 | */ 134 | private static byte[] shortToByteArray(short data) { 135 | return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(data).array(); 136 | } 137 | 138 | /** 139 | * 将byte[]转化为short 140 | */ 141 | private static short byteArrayToShort(byte[] b) { 142 | return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getShort(); 143 | } 144 | 145 | /** 146 | * 将byte[]转化为int 147 | */ 148 | private static int byteArrayToInt(byte[] b) { 149 | return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt(); 150 | } 151 | 152 | /** 153 | * 头部部分信息 154 | */ 155 | static class Header { 156 | public int fileSize; 157 | public int fileSizeOffset; 158 | public int dataSize; 159 | public int dataSizeOffset; 160 | public int dataOffset; 161 | public FileInputStream dataInputStream; 162 | } 163 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/callback/DecodeOperateInterface.java: -------------------------------------------------------------------------------- 1 | package com.audio.util.callback; 2 | 3 | /** 4 | * 音频解码监听器 5 | */ 6 | public interface DecodeOperateInterface { 7 | public void updateDecodeProgress(int decodeProgress); 8 | 9 | public void decodeSuccess(); 10 | 11 | public void decodeFail(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/cut/AudioCutUtil.java: -------------------------------------------------------------------------------- 1 | package com.audio.util.cut; 2 | 3 | import android.util.Log; 4 | 5 | import com.audio.util.AudioEditUtil; 6 | import com.audio.util.DecodeEngine; 7 | import com.audio.util.Audio; 8 | import com.audio.util.callback.DecodeOperateInterface; 9 | import com.audio.util.util.FileUtils; 10 | 11 | import java.io.File; 12 | 13 | public class AudioCutUtil { 14 | public interface AudioCutCallback{ 15 | public void onFinish(String pathFinished,String msg); 16 | public void onCutError(String msg); 17 | } 18 | /** 19 | * 裁剪音频 20 | * @param srcPath 源音频路径 21 | * @param destPath 目标音频路径 22 | * @param startTime 裁剪开始时间 23 | * @param endTime 裁剪结束时间 24 | */ 25 | public static void cutAudio(String srcPath,String destPath, float startTime, float endTime){ 26 | cutAudio(srcPath,destPath,startTime,endTime,null); 27 | } 28 | /** 29 | * 裁剪音频 30 | * @param srcPath 源音频路径 31 | * @param destPath 目标音频路径 32 | * @param startTime 裁剪开始时间 33 | * @param endTime 裁剪结束时间 34 | */ 35 | public static void cutAudio(String srcPath, String destPath,float startTime, float endTime,AudioCutCallback callback){ 36 | String fileName = new File(srcPath).getName(); 37 | //String nameNoSuffix = fileName.substring(0, fileName.lastIndexOf('.')); 38 | //fileName = nameNoSuffix + Constant.SUFFIX_WAV; 39 | //String outName = nameNoSuffix + "_cut.wav"; 40 | 41 | //裁剪后音频的路径 42 | //String destPath = FileUtils.getAudioEditStorageDirectory() + File.separator + outName; 43 | Log.d("ddebug","cut destPath = " + destPath); 44 | 45 | //解码源音频,得到解码后的文件 46 | decodeAudio(srcPath, destPath); 47 | 48 | if(!FileUtils.checkFileExist(destPath)){ 49 | if(callback != null){ 50 | callback.onCutError("解码失败" + destPath); 51 | } 52 | Log.d("ddebug","解码失败" + destPath); 53 | //ToastUtil.showToast("解码失败" + destPath); 54 | return; 55 | } 56 | 57 | Audio audio = getAudioFromPath(destPath); 58 | 59 | if(audio != null){ 60 | AudioEditUtil.cutAudio(audio, startTime, endTime); 61 | } 62 | 63 | String msg = "裁剪完成"; 64 | Log.d("ddebug",msg + "cut destPath = " + destPath); 65 | if(callback != null){ 66 | callback.onFinish(destPath,msg); 67 | } 68 | //EventBus.getDefault().post(new AudioMsg(AudioTaskCreator.ACTION_AUDIO_CUT, destPath, msg)); 69 | } 70 | private static void decodeAudio(String path, String destPath){ 71 | final File file = new File(path); 72 | 73 | if(FileUtils.checkFileExist(destPath)){ 74 | FileUtils.deleteFile(new File(destPath)); 75 | } 76 | 77 | FileUtils.confirmFolderExist(new File(destPath).getParent()); 78 | 79 | DecodeEngine.getInstance().convertMusicFileToWaveFile(path, destPath, new DecodeOperateInterface() { 80 | @Override public void updateDecodeProgress(int decodeProgress) { 81 | String msg = String.format("解码文件:%s,进度:%d", file.getName(), decodeProgress) + "%"; 82 | //EventBus.getDefault().post(new AudioMsg(AudioTaskCreator.ACTION_AUDIO_MIX, msg)); 83 | } 84 | 85 | @Override public void decodeSuccess() { 86 | 87 | } 88 | 89 | @Override public void decodeFail() { 90 | 91 | } 92 | }); 93 | } 94 | /** 95 | * 获取根据解码后的文件得到audio数据 96 | * @param path 97 | * @return 98 | */ 99 | private static Audio getAudioFromPath(String path){ 100 | if(!FileUtils.checkFileExist(path)){ 101 | return null; 102 | } 103 | 104 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { 105 | try { 106 | Audio audio = Audio.createAudioFromFile(new File(path)); 107 | return audio; 108 | } catch (Exception e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | 113 | return null; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/encode/CommonFunction.java: -------------------------------------------------------------------------------- 1 | package com.audio.util.encode; 2 | 3 | import android.os.Looper; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Locale; 6 | 7 | /** 8 | */ 9 | public class CommonFunction { 10 | public static String FormatRecordTime(int recordTime) { 11 | int minute = recordTime / 60; 12 | int second = recordTime % 60; 13 | 14 | String formatRecordTime = ""; 15 | 16 | if (minute != 0) { 17 | formatRecordTime += String.valueOf(minute) + "′"; 18 | } 19 | 20 | formatRecordTime += String.valueOf(second) + "″"; 21 | 22 | return formatRecordTime; 23 | } 24 | 25 | public static String GetDate() { 26 | long time = System.currentTimeMillis(); 27 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA); 28 | String date = simpleDateFormat.format(time); 29 | return date; 30 | } 31 | 32 | public static String GetDate(long time) { 33 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA); 34 | String date = simpleDateFormat.format(time); 35 | return date; 36 | } 37 | 38 | public static boolean notEmpty(CharSequence text) { 39 | return !isEmpty(text); 40 | } 41 | 42 | public static boolean isEmpty(CharSequence text) { 43 | if (text == null || text.length() == 0) { 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | public static byte[] GetBytes(short shortValue, boolean bigEnding) { 51 | byte[] byteArray = new byte[2]; 52 | 53 | if (bigEnding) { 54 | byteArray[1] = (byte) (shortValue & 0x00ff); 55 | shortValue >>= 8; 56 | byteArray[0] = (byte) (shortValue & 0x00ff); 57 | } else { 58 | byteArray[0] = (byte) (shortValue & 0x00ff); 59 | shortValue >>= 8; 60 | byteArray[1] = (byte) (shortValue & 0x00ff); 61 | } 62 | 63 | return byteArray; 64 | } 65 | 66 | public static short[] GetShorts(int value, boolean bigEnding) { 67 | short[] byteArray = new short[2]; 68 | 69 | if (bigEnding) { 70 | byteArray[1] = (byte) (value & 0x00ffff); 71 | value >>= 8 * 2; 72 | byteArray[0] = (byte) (value & 0x00ffff); 73 | } else { 74 | byteArray[0] = (byte) (value & 0x00ffff); 75 | value >>= 8 * 2; 76 | byteArray[1] = (byte) (value & 0x00ffff); 77 | } 78 | 79 | return byteArray; 80 | } 81 | 82 | public static short GetShort(byte firstByte, byte secondByte, boolean bigEnding) { 83 | short shortValue = 0; 84 | 85 | if (bigEnding) { 86 | shortValue |= (firstByte & 0x00ff); 87 | shortValue <<= 8; 88 | shortValue |= (secondByte & 0x00ff); 89 | } else { 90 | shortValue |= (secondByte & 0x00ff); 91 | shortValue <<= 8; 92 | shortValue |= (firstByte & 0x00ff); 93 | } 94 | 95 | return shortValue; 96 | } 97 | 98 | public static short GetInt(byte firstByte, byte secondByte, byte thirdByte, byte fourthByte, 99 | boolean bigEnding) { 100 | short shortValue = 0; 101 | 102 | if (bigEnding) { 103 | shortValue |= (firstByte <<= 8 * 3); 104 | shortValue |= (secondByte <<= 8 * 2); 105 | shortValue |= (thirdByte <<= 8 * 1); 106 | shortValue |= (fourthByte <<= 8 * 0); 107 | } else { 108 | shortValue |= (firstByte <<= 8 * 0); 109 | shortValue |= (secondByte <<= 8 * 1); 110 | shortValue |= (thirdByte <<= 8 * 2); 111 | shortValue |= (fourthByte <<= 8 * 3); 112 | } 113 | 114 | return shortValue; 115 | } 116 | 117 | public static byte[] AverageShortByteArray(byte firstShortHighByte, byte firstShortLowByte, 118 | byte secondShortHighByte, byte secondShortLowByte, boolean bigEnding) { 119 | short firstShort = CommonFunction.GetShort(firstShortHighByte, firstShortLowByte, bigEnding); 120 | short secondShort = CommonFunction.GetShort(secondShortHighByte, secondShortLowByte, bigEnding); 121 | return CommonFunction.GetBytes((short) (firstShort / 2 + secondShort / 2), bigEnding); 122 | } 123 | 124 | public static short AverageShort(byte firstShortHighByte, byte firstShortLowByte, 125 | byte secondShortHighByte, byte secondShortLowByte, boolean bigEnding) { 126 | short firstShort = CommonFunction.GetShort(firstShortHighByte, firstShortLowByte, bigEnding); 127 | short secondShort = CommonFunction.GetShort(secondShortHighByte, secondShortLowByte, bigEnding); 128 | return (short) (firstShort / 2 + secondShort / 2); 129 | } 130 | 131 | public static short WeightShort(byte firstShortHighByte, byte firstShortLowByte, 132 | byte secondShortHighByte, byte secondShortLowByte, float firstWeight, float secondWeight, 133 | boolean bigEnding) { 134 | short firstShort = CommonFunction.GetShort(firstShortHighByte, firstShortLowByte, bigEnding); 135 | short secondShort = CommonFunction.GetShort(secondShortHighByte, secondShortLowByte, bigEnding); 136 | return (short) (firstShort * firstWeight + secondShort * secondWeight); 137 | } 138 | 139 | public static boolean IsInMainThread() { 140 | return Looper.myLooper() == Looper.getMainLooper(); 141 | } 142 | 143 | public static byte[] GetByteBuffer(short[] shortArray, boolean bigEnding) { 144 | return GetByteBuffer(shortArray, shortArray.length, bigEnding); 145 | } 146 | 147 | public static byte[] GetByteBuffer(short[] shortArray, int shortArrayLength, boolean bigEnding) { 148 | int actualShortArrayLength = shortArray.length; 149 | 150 | if (shortArrayLength > actualShortArrayLength) { 151 | shortArrayLength = actualShortArrayLength; 152 | } 153 | 154 | short shortValue; 155 | byte[] byteArray = new byte[2 * shortArrayLength]; 156 | 157 | for (int i = 0; i < shortArrayLength; i++) { 158 | shortValue = shortArray[i]; 159 | 160 | if (bigEnding) { 161 | byteArray[i * 2 + 1] = (byte) (shortValue & 0x00ff); 162 | shortValue >>= 8; 163 | byteArray[i * 2] = (byte) (shortValue & 0x00ff); 164 | } else { 165 | byteArray[i * 2] = (byte) (shortValue & 0x00ff); 166 | shortValue >>= 8; 167 | byteArray[i * 2 + 1] = (byte) (shortValue & 0x00ff); 168 | } 169 | } 170 | 171 | return byteArray; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/record/AudioRecordUtil.kt: -------------------------------------------------------------------------------- 1 | package com.audio.util.record 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.media.AudioFormat 7 | import android.media.AudioRecord 8 | import android.media.MediaRecorder 9 | import android.os.Handler 10 | import android.os.HandlerThread 11 | import android.text.TextUtils 12 | import android.util.Log 13 | import androidx.core.content.ContextCompat 14 | import com.audio.util.ConvertUtil 15 | import kotlinx.android.synthetic.main.activity_record.* 16 | import java.io.File 17 | import java.io.FileNotFoundException 18 | import java.io.FileOutputStream 19 | import java.io.IOException 20 | 21 | 22 | /** 23 | * 音频录制回调接口 24 | */ 25 | interface RecordCallback{ 26 | fun onRecordError(errorMsg:String) 27 | fun onRecordStatusChanged(isInRecording:Boolean) 28 | //fun onRecordFinish(path:String) 29 | } 30 | public interface FinishCallback{ 31 | fun onFinish() 32 | } 33 | class AudioRecordUtil { 34 | 35 | companion object{ 36 | 37 | private val TAG = "AudioRecordUtil" 38 | /** 39 | * 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。 40 | */ 41 | private val SAMPLE_RATE_INHZ = 44100 42 | 43 | /** 44 | * 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 45 | */ 46 | private val CHANNEL_CONFIG: Int = AudioFormat.CHANNEL_IN_STEREO 47 | 48 | /** 49 | * 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT. 50 | */ 51 | private val AUDIO_FORMAT: Int = AudioFormat.ENCODING_PCM_16BIT 52 | 53 | 54 | /** 55 | * 音频录制类 56 | */ 57 | private var audioRecord: AudioRecord? = null; 58 | 59 | /** 60 | * 工作线程 61 | */ 62 | private lateinit var handlerThread: HandlerThread 63 | 64 | /** 65 | * 工作调度类 66 | */ 67 | private lateinit var workHandler: Handler 68 | 69 | /** 70 | * 是否正在录制中 71 | */ 72 | private var isRecording = false 73 | 74 | /** 75 | * 当前正在录制的输出音频文件路径 76 | */ 77 | private var currentOutAudioPath:String? = null 78 | 79 | 80 | 81 | 82 | /** 83 | *初始化录制控件 84 | * sampleRateInHz 采样率 85 | * channelConfig 声道数 86 | * audioFormat 采样位数 87 | */ 88 | private fun init(sampleRateInHz:Int, channelConfig:Int, audioFormat:Int) { 89 | val minBufferSize = 90 | AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) 91 | //MediaRecorder.AudioSource.MIC 92 | audioRecord = AudioRecord( 93 | MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRateInHz, 94 | channelConfig, audioFormat, minBufferSize 95 | ) 96 | 97 | handlerThread = HandlerThread("Record"); 98 | handlerThread.start(); 99 | workHandler = Handler(handlerThread.getLooper()); 100 | } 101 | 102 | /** 103 | * 重新开始音频录制 104 | */ 105 | @JvmStatic 106 | fun reStartRecord(context: Context, wavOutFilePath: String, callback: RecordCallback) { 107 | giveUp() 108 | startRecord(context,wavOutFilePath,callback) 109 | } 110 | 111 | /** 112 | * 开始录音 113 | * wavOutFilePath wav录音文件输出路径 114 | */ 115 | @JvmStatic 116 | public fun startRecord(context: Context, wavOutFilePath:String){ 117 | startRecord(context,wavOutFilePath,null) 118 | } 119 | /** 120 | * 开始录音 121 | * wavOutFilePath wav录音文件输出路径 122 | */ 123 | @JvmStatic 124 | public fun startRecord(context: Context, wavOutFilePath:String, callback: RecordCallback?){ 125 | startRecord(context,SAMPLE_RATE_INHZ, CHANNEL_CONFIG, AUDIO_FORMAT,wavOutFilePath,callback) 126 | } 127 | /** 128 | * 开始录音 129 | * sampleRateInHz 采样率 130 | * channelConfig 声道数 131 | * audioFormat 采样位数 132 | * wavOutFilePath wav录音文件输出路径 133 | */ 134 | @JvmStatic 135 | public fun startRecord(context: Context, sampleRateInHz:Int, channelConfig:Int, audioFormat:Int, wavOutFilePath:String) { 136 | startRecord(context,sampleRateInHz, channelConfig, audioFormat,wavOutFilePath,null) 137 | } 138 | /** 139 | * 开始录音 140 | * sampleRateInHz 采样率 141 | * channelConfig 声道数 142 | * audioFormat 采样位数 143 | * callback 录制回调 144 | * wavOutFilePath wav录音文件输出路径 145 | */ 146 | @JvmStatic 147 | public fun startRecord(context: Context, sampleRateInHz:Int, channelConfig:Int, audioFormat:Int, wavOutFilePath:String, callback: RecordCallback?) { 148 | //情况判断 权限 录制状态 149 | 150 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){ 151 | callback?.onRecordError( "${Manifest.permission.RECORD_AUDIO} 权限未授予") 152 | return 153 | } 154 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ 155 | callback?.onRecordError( "${Manifest.permission.WRITE_EXTERNAL_STORAGE} 权限未授权") 156 | return 157 | } 158 | if(isRecording){ 159 | callback?.onRecordError("已有任务正在执行,同一时间只能执行一个录制任务,不能重复执行录制") 160 | return 161 | } 162 | if(!TextUtils.isEmpty(currentOutAudioPath) && !TextUtils.equals(wavOutFilePath, currentOutAudioPath)){ 163 | callback?.onRecordError("$currentOutAudioPath 任务正在执行,同一时间只能有一个录制任务") 164 | return 165 | } 166 | 167 | init(sampleRateInHz,channelConfig,audioFormat) 168 | 169 | // 获取最小录音缓存大小, 170 | val minBufferSize = 171 | AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) 172 | 173 | // 初始化缓存 174 | val data = ByteArray(minBufferSize) 175 | 176 | val audioPcmPath = getPcmPath(wavOutFilePath) 177 | val file = File(audioPcmPath) 178 | Log.i(TAG, "path:" + file.getAbsolutePath()) 179 | //tv.append("\n"+"文件位置:" + file.getAbsolutePath());//tvRecordFilePosition.setText("文件位置:" + file.getAbsolutePath()) 180 | if(!file.exists()){ 181 | file.parentFile.mkdirs() 182 | 183 | if (!file.createNewFile()) { 184 | val msg = "文件未能创建成功:" + file.getAbsolutePath() 185 | 186 | callback?.onRecordError(msg) 187 | 188 | Log.d(TAG, msg) 189 | return 190 | } 191 | } 192 | 193 | // 开始录音 194 | audioRecord?.startRecording() 195 | 196 | isRecording = true 197 | currentOutAudioPath = wavOutFilePath 198 | 199 | callback?.onRecordStatusChanged(isRecording) 200 | 201 | // 创建数据流,将缓存导入数据流 202 | workHandler.post(Runnable { 203 | var fos: FileOutputStream? = null 204 | try { 205 | fos = FileOutputStream(file,true) 206 | } catch (e: FileNotFoundException) { 207 | e.printStackTrace() 208 | 209 | if(callback != null){ 210 | e.message?.let { callback.onRecordError(it) } 211 | } 212 | 213 | val msg = "文件未找到" 214 | Log.e(TAG,msg) 215 | //tv.append("\n" + msg) 216 | } 217 | if (fos == null) return@Runnable 218 | while (isRecording) { 219 | val length: Int = audioRecord!!.read(data, 0, minBufferSize) 220 | if (AudioRecord.ERROR_INVALID_OPERATION != length) { 221 | try { 222 | fos.write(data, 0, length) 223 | } catch (e: IOException) { 224 | e.printStackTrace() 225 | } 226 | } 227 | } 228 | try { 229 | // 关闭数据流 230 | fos.close() 231 | } catch (e: IOException) { 232 | e.printStackTrace() 233 | }finally { 234 | callback?.onRecordStatusChanged(isRecording) 235 | } 236 | }) 237 | } 238 | /** 239 | * 暂停录音 240 | */ 241 | @JvmStatic 242 | public fun pause() { 243 | isRecording = false 244 | audioRecord?.let { 245 | it.stop() 246 | it.release() 247 | audioRecord = null 248 | } 249 | } 250 | 251 | /** 252 | * 完成录制; 253 | * 建议在字线程调用处理 254 | */ 255 | @JvmStatic 256 | fun finishRecord(callback: FinishCallback?){ 257 | if(isRecording){ 258 | pause(); 259 | } 260 | Log.d("ddebug","finishRecord currentOutAudioPath = $currentOutAudioPath") 261 | val audioPcmPath = currentOutAudioPath?.let { getPcmPath(it) } 262 | ConvertUtil.convertPcm2WavBitNum16(audioPcmPath, currentOutAudioPath) 263 | audioPcmPath?.let { File(audioPcmPath).delete() } 264 | currentOutAudioPath = null 265 | callback?.let { 266 | it.onFinish() 267 | } 268 | } 269 | /** 270 | * 根据wav文件输出路径得到出pcm文件临时目录 271 | */ 272 | private fun getPcmPath(wavPath:String):String{ 273 | val buffer:StringBuffer = StringBuffer(wavPath) 274 | buffer.append(".tmp.pcm") 275 | return buffer.toString() 276 | } 277 | 278 | /** 279 | * 丢弃已录制的音频 280 | */ 281 | @JvmStatic 282 | fun giveUp() { 283 | if(isRecording){ 284 | pause() 285 | } 286 | Log.d("ddebug","放弃录制项:currentOutAudioPath = $currentOutAudioPath") 287 | currentOutAudioPath?.let { 288 | val audioPcmPath = getPcmPath(it) 289 | var file1 = File(audioPcmPath) 290 | var file2 = File(currentOutAudioPath) 291 | Log.d("ddebug", "放弃录制项: exists1 = ${file1.exists()} exists2 = ${file2.exists()}") 292 | var delete1 = file1.delete() 293 | var delete2 = file2.delete() 294 | Log.d("ddebug","放弃录制项: $delete1 $delete2") 295 | Log.d("ddebug","放弃录制项:$audioPcmPath $currentOutAudioPath") 296 | } 297 | currentOutAudioPath = null 298 | } 299 | 300 | 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/ssrc/I0Bessel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright(C) 1996 Takuya OOURA (email: ooura@mmm.t.u-tokyo.ac.jp). 3 | * You may use, copy, modify this code for any purpose and 4 | * without fee. You may distribute this ORIGINAL package. 5 | */ 6 | package com.audio.util.ssrc; 7 | 8 | 9 | /** 10 | * Bessel I_0(). 11 | * 12 | * @author Takuya OOURA 13 | * @author Naohide Sano (nsano) 14 | * @version 0.00 060127 nsano port to java version
15 | */ 16 | public class I0Bessel { 17 | /** */ 18 | private static final double[] a = { 19 | 8.5246820682016865877e-11, 2.5966600546497407288e-9, 20 | 7.9689994568640180274e-8, 1.9906710409667748239e-6, 21 | 4.0312469446528002532e-5, 6.4499871606224265421e-4, 22 | 0.0079012345761930579108, 0.071111111109207045212, 23 | 0.444444444444724909, 1.7777777777777532045, 24 | 4.0000000000000011182, 3.99999999999999998, 25 | 1.0000000000000000001, 26 | 1.1520919130377195927e-10, 2.2287613013610985225e-9, 27 | 8.1903951930694585113e-8, 1.9821560631611544984e-6, 28 | 4.0335461940910133184e-5, 6.4495330974432203401e-4, 29 | 0.0079013012611467520626, 0.071111038160875566622, 30 | 0.44444450319062699316, 1.7777777439146450067, 31 | 4.0000000132337935071, 3.9999999968569015366, 32 | 1.0000000003426703174, 33 | 1.5476870780515238488e-10, 1.2685004214732975355e-9, 34 | 9.2776861851114223267e-8, 1.9063070109379044378e-6, 35 | 4.0698004389917945832e-5, 6.4370447244298070713e-4, 36 | 0.0079044749458444976958, 0.071105052411749363882, 37 | 0.44445280640924755082, 1.7777694934432109713, 38 | 4.0000055808824003386, 3.9999977081165740932, 39 | 1.0000004333949319118, 40 | 2.0675200625006793075e-10, -6.1689554705125681442e-10, 41 | 1.2436765915401571654e-7, 1.5830429403520613423e-6, 42 | 4.2947227560776583326e-5, 6.3249861665073441312e-4, 43 | 0.0079454472840953930811, 0.070994327785661860575, 44 | 0.44467219586283000332, 1.7774588182255374745, 45 | 4.0003038986252717972, 3.9998233869142057195, 46 | 1.0000472932961288324, 47 | 2.7475684794982708655e-10, -3.8991472076521332023e-9, 48 | 1.9730170483976049388e-7, 5.9651531561967674521e-7, 49 | 5.1992971474748995357e-5, 5.7327338675433770752e-4, 50 | 0.0082293143836530412024, 0.069990934858728039037, 51 | 0.44726764292723985087, 1.7726685170014087784, 52 | 4.0062907863712704432, 3.9952750700487845355, 53 | 1.0016354346654179322 54 | }; 55 | /** */ 56 | private static final double[] b = { 57 | 6.7852367144945531383e-8, 4.6266061382821826854e-7, 58 | 6.9703135812354071774e-6, 7.6637663462953234134e-5, 59 | 7.9113515222612691636e-4, 0.0073401204731103808981, 60 | 0.060677114958668837046, 0.43994941411651569622, 61 | 2.7420017097661750609, 14.289661921740860534, 62 | 59.820609640320710779, 188.78998681199150629, 63 | 399.8731367825601118, 427.56411572180478514, 64 | 1.8042097874891098754e-7, 1.2277164312044637357e-6, 65 | 1.8484393221474274861e-5, 2.0293995900091309208e-4, 66 | 0.0020918539850246207459, 0.019375315654033949297, 67 | 0.15985869016767185908, 1.1565260527420641724, 68 | 7.1896341224206072113, 37.354773811947484532, 69 | 155.80993164266268457, 489.5211371158540918, 70 | 1030.9147225169564806, 1093.5883545113746958, 71 | 4.8017305613187493564e-7, 3.261317843912380074e-6, 72 | 4.9073137508166159639e-5, 5.3806506676487583755e-4, 73 | 0.0055387918291051866561, 0.051223717488786549025, 74 | 0.42190298621367914765, 3.0463625987357355872, 75 | 18.895299447327733204, 97.915189029455461554, 76 | 407.13940115493494659, 1274.3088990480582632, 77 | 2670.9883037012547506, 2815.7166284662544712, 78 | 1.2789926338424623394e-6, 8.6718263067604918916e-6, 79 | 1.3041508821299929489e-4, 0.001428224737372747892, 80 | 0.014684070635768789378, 0.13561403190404185755, 81 | 1.1152592585977393953, 8.0387088559465389038, 82 | 49.761318895895479206, 257.2684232313529138, 83 | 1066.8543146269566231, 3328.3874581009636362, 84 | 6948.8586598121634874, 7288.4893398212481055, 85 | 3.409350368197032893e-6, 2.3079025203103376076e-5, 86 | 3.4691373283901830239e-4, 0.003794994977222908545, 87 | 0.038974209677945602145, 0.3594948380414878371, 88 | 2.9522878893539528226, 21.246564609514287056, 89 | 131.28727387146173141, 677.38107093296675421, 90 | 2802.3724744545046518, 8718.5731420798254081, 91 | 18141.348781638832286, 18948.925349296308859 92 | }; 93 | /** */ 94 | private static final double[] c = { 95 | 2.5568678676452702768e-15, 3.0393953792305924324e-14, 96 | 6.3343751991094840009e-13, 1.5041298011833009649e-11, 97 | 4.4569436918556541414e-10, 1.746393051427167951e-8, 98 | 1.0059224011079852317e-6, 1.0729838945088577089e-4, 99 | 0.05150322693642527738, 100 | 5.2527963991711562216e-15, 7.202118481421005641e-15, 101 | 7.2561421229904797156e-13, 1.482312146673104251e-11, 102 | 4.4602670450376245434e-10, 1.7463600061788679671e-8, 103 | 1.005922609132234756e-6, 1.0729838937545111487e-4, 104 | 0.051503226936437300716, 105 | 1.3365917359358069908e-14, -1.2932643065888544835e-13, 106 | 1.7450199447905602915e-12, 1.0419051209056979788e-11, 107 | 4.58047881980598326e-10, 1.7442405450073548966e-8, 108 | 1.0059461453281292278e-6, 1.0729837434500161228e-4, 109 | 0.051503226940658446941, 110 | 5.3771611477352308649e-14, -1.1396193006413731702e-12, 111 | 1.2858641335221653409e-11, -5.9802086004570057703e-11, 112 | 7.3666894305929510222e-10, 1.6731837150730356448e-8, 113 | 1.0070831435812128922e-6, 1.0729733111203704813e-4, 114 | 0.051503227360726294675, 115 | 3.7819492084858931093e-14, -4.8600496888588034879e-13, 116 | 1.6898350504817224909e-12, 4.5884624327524255865e-11, 117 | 1.2521615963377513729e-10, 1.8959658437754727957e-8, 118 | 1.0020716710561353622e-6, 1.073037119856927559e-4, 119 | 0.05150322383300230775 120 | }; 121 | 122 | /** 123 | * 124 | * @param x 125 | * @return 126 | */ 127 | public static double value(double x) { 128 | int k; 129 | double w, t, y; 130 | w = Math.abs(x); 131 | if (w < 8.5) { 132 | t = w * w * 0.0625; 133 | k = 13 * ((int) t); 134 | y = (((((((((((a[k] * t + a[k + 1]) * t + 135 | a[k + 2]) * t + a[k + 3]) * t + a[k + 4]) * t + 136 | a[k + 5]) * t + a[k + 6]) * t + a[k + 7]) * t + 137 | a[k + 8]) * t + a[k + 9]) * t + a[k + 10]) * t + 138 | a[k + 11]) * t + a[k + 12]; 139 | } else if (w < 12.5) { 140 | k = (int) w; 141 | t = w - k; 142 | k = 14 * (k - 8); 143 | y = ((((((((((((b[k] * t + b[k + 1]) * t + 144 | b[k + 2]) * t + b[k + 3]) * t + b[k + 4]) * t + 145 | b[k + 5]) * t + b[k + 6]) * t + b[k + 7]) * t + 146 | b[k + 8]) * t + b[k + 9]) * t + b[k + 10]) * t + 147 | b[k + 11]) * t + b[k + 12]) * t + b[k + 13]; 148 | } else { 149 | t = 60 / w; 150 | k = 9 * ((int) t); 151 | y = ((((((((c[k] * t + c[k + 1]) * t + 152 | c[k + 2]) * t + c[k + 3]) * t + c[k + 4]) * t + 153 | c[k + 5]) * t + c[k + 6]) * t + c[k + 7]) * t + 154 | c[k + 8]) * Math.sqrt(t) * Math.exp(w); 155 | } 156 | return y; 157 | } 158 | } 159 | 160 | /* */ 161 | -------------------------------------------------------------------------------- /src/main/java/com/audio/util/util/Constant.java: -------------------------------------------------------------------------------- 1 | package com.audio.util.util; 2 | 3 | public class Constant { 4 | public static final boolean Debug = true;//BuildConfig.DEBUG; 5 | public static final String NAME = "AudioEdit"; 6 | 7 | public static final int ExportChannelNumber = 2; // 输出声道为双声道 8 | public static final int ExportByteNumber = 2; //输出采样精度字节数 9 | public static final int ExportSampleRate = 44100; //输出采样率 10 | 11 | public static final int OneSecond = 1000; 12 | 13 | public static final int NormalMaxProgress = 100; 14 | 15 | public static boolean isBigEnding = false; 16 | 17 | public static String SUFFIX_WAV = ".wav"; 18 | public static String SUFFIX_PCM = ".pcm"; 19 | 20 | 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/audio/util/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.audio.util.util; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.media.MediaPlayer; 6 | import android.net.Uri; 7 | import android.os.Environment; 8 | import android.text.TextUtils; 9 | 10 | import com.audio.util.LogUtil; 11 | 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileOutputStream; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | 18 | public class FileUtils { 19 | 20 | public static final String SAVE_FOLDER = "/" + Constant.NAME; 21 | public static final String SAVE_AUDIO_FOLDER = "/audio"; 22 | public static final String SAVE_LOG_FOLDER = "/log"; 23 | 24 | /** 25 | * sd卡的根目录 26 | */ 27 | public static String sSdRootPath = Environment.getExternalStorageDirectory().getPath(); 28 | 29 | /** 30 | * 手机的缓存根目录 31 | */ 32 | public static String sDataRootPath = Environment.getDownloadCacheDirectory().getPath();//App.getInstance().getCacheDir().getPath(); 33 | 34 | /** 35 | * 获取存储的根目录 36 | */ 37 | public static String getRootDirectory() { 38 | 39 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ? sSdRootPath 40 | + SAVE_FOLDER : sDataRootPath + SAVE_FOLDER; 41 | } 42 | 43 | /** 44 | * 获取缓存存储的根目录 45 | */ 46 | public static String getCacheRootDirectory(Context context) { 47 | 48 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 49 | return context.getExternalCacheDir().getAbsolutePath(); 50 | } else { 51 | return context.getCacheDir().getAbsolutePath(); 52 | } 53 | } 54 | 55 | /** 56 | * 获取储存日志的目录 57 | */ 58 | public static String getLogStorageDirectory() { 59 | 60 | return getRootDirectory() + SAVE_LOG_FOLDER; 61 | } 62 | 63 | /** 64 | * 获取储存录音的目录 65 | */ 66 | public static String getAudioStorageDirectory() { 67 | 68 | return getRootDirectory() + SAVE_AUDIO_FOLDER; 69 | } 70 | 71 | /** 72 | * 获取音频编辑文件夹的目录 73 | */ 74 | public static String getAudioEditStorageDirectory() { 75 | 76 | return getRootDirectory() + SAVE_AUDIO_FOLDER; 77 | } 78 | 79 | /** 80 | * 外置SD是否可用 81 | */ 82 | public static boolean isExternalStrorageExsist() { 83 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 84 | } 85 | 86 | /** 87 | * 获取生成时间 88 | */ 89 | public static String getFileBuildTime(File file) { 90 | Date date = new Date(file.lastModified());//最后更新的时间 91 | String t; 92 | SimpleDateFormat sy2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置年月日时分秒 93 | t = sy2.format(date); 94 | return t; 95 | } 96 | 97 | /** 98 | * 获取时间长度 99 | */ 100 | public static String getFilePlayTimeString(Context context, File file) { 101 | Date date; 102 | SimpleDateFormat sy1; 103 | String dateFormat = "error"; 104 | 105 | int duration = getFilePlayTime(context, file); 106 | 107 | sy1 = new SimpleDateFormat("HH:mm:ss");//设置为时分秒的格式 108 | try { 109 | 110 | //使用Date格式化播放时间mediaPlayer.getDuration() 111 | if(duration < 60 * 60 * 1000){ 112 | sy1 = new SimpleDateFormat("mm:ss");//设置为时分秒的格式 113 | date = sy1.parse("00:00"); 114 | }else{ 115 | date = sy1.parse("00:00:00"); 116 | } 117 | 118 | date.setTime(duration + date.getTime());//用消除date.getTime()时区差 119 | dateFormat = sy1.format(date); 120 | 121 | }catch (Exception ex){ 122 | ex.printStackTrace(); 123 | } 124 | 125 | return dateFormat; 126 | } 127 | 128 | /** 129 | * 获取时间长度 130 | */ 131 | public static int getFilePlayTime(Context context, File file) { 132 | try { 133 | MediaPlayer mediaPlayer = MediaPlayer.create(context, Uri.parse(file.toString())); 134 | //使用Date格式化播放时间mediaPlayer.getDuration() 135 | int duration = mediaPlayer.getDuration(); 136 | 137 | mediaPlayer.release(); 138 | 139 | return duration; 140 | } catch (Exception e) { 141 | e.printStackTrace(); 142 | } 143 | return 0; 144 | } 145 | 146 | /** 147 | * 删除文件 148 | */ 149 | public static void deleteFile(String filePath) { 150 | if (TextUtils.isEmpty(filePath)) return; 151 | deleteFile(new File(filePath)); 152 | } 153 | 154 | /** 155 | * 确保目录存在,没有则创建 156 | */ 157 | public static boolean confirmFolderExist(String folderPath) { 158 | 159 | File file = new File(folderPath); 160 | if (!file.exists()) { 161 | return file.mkdirs(); 162 | } 163 | 164 | return false; 165 | } 166 | 167 | /** 168 | * 复制单个文件 169 | * 170 | * @param srcPath String 原文件路径 171 | * @param destPath String 复制后路径 172 | * @return boolean 173 | */ 174 | public static void copyFile(String srcPath, String destPath) { 175 | FileInputStream fis = null; 176 | FileOutputStream fos = null; 177 | 178 | try { 179 | 180 | if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(destPath) || TextUtils.equals(srcPath, 181 | destPath)) { 182 | return; 183 | } 184 | 185 | LogUtil.i("-----------" + new File(destPath).exists()); 186 | 187 | File destFile = new File(destPath); 188 | if (!destFile.getParentFile().exists()) { 189 | LogUtil.i("-----------" + new File(destPath).exists()); 190 | destFile.getParentFile().mkdirs(); 191 | } 192 | 193 | new File(destPath).delete(); 194 | String tempPath = srcPath + ".temp"; 195 | 196 | File oldfile = new File(srcPath); 197 | if (oldfile.exists()) { //文件存在时 198 | fis = new FileInputStream(srcPath); //读入原文件 199 | fos = new FileOutputStream(tempPath); 200 | byte[] buffer = new byte[1024]; 201 | int length = 0; 202 | while ((length = fis.read(buffer)) != -1) { 203 | fos.write(buffer, 0, length); 204 | } 205 | } 206 | 207 | fos.close(); 208 | fis.close(); 209 | 210 | LogUtil.i("-----------" + new File(tempPath).renameTo(new File(destPath))); 211 | } catch (Exception e) { 212 | e.printStackTrace(); 213 | } 214 | } 215 | 216 | /** 217 | * 复制单个文件 218 | * 219 | * @param srcPath String 原文件路径 220 | * @param destPath String 复制后路径 221 | * @return boolean 222 | */ 223 | public static void copyFile2(String srcPath, String destPath) { 224 | FileInputStream fis = null; 225 | FileOutputStream fos = null; 226 | 227 | try { 228 | 229 | if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(destPath) || TextUtils.equals(srcPath, 230 | destPath)) { 231 | return; 232 | } 233 | 234 | LogUtil.i("-----------" + new File(destPath).exists()); 235 | 236 | File destFile = new File(destPath); 237 | if (!destFile.getParentFile().exists()) { 238 | destFile.getParentFile().mkdirs(); 239 | } 240 | 241 | if (destFile.exists()) { 242 | destFile.delete(); 243 | } 244 | 245 | fis = new FileInputStream(srcPath); //读入原文件 246 | fos = new FileOutputStream(destPath); 247 | byte[] buffer = new byte[1024]; 248 | int length = 0; 249 | while ((length = fis.read(buffer)) != -1) { 250 | fos.write(buffer, 0, length); 251 | } 252 | } catch (Exception e) { 253 | e.printStackTrace(); 254 | } finally { 255 | try { 256 | fos.close(); 257 | } catch (Exception ex) { 258 | ex.printStackTrace(); 259 | } 260 | try { 261 | fis.close(); 262 | } catch (Exception ex) { 263 | ex.printStackTrace(); 264 | } 265 | } 266 | } 267 | 268 | /** 269 | * 删除文件夹所有内容 270 | */ 271 | public static void deleteFile(File file) { 272 | if (file != null && file.exists()) { // 判断文件是否存在 273 | if (file.isDirectory()) { // 否则如果它是一个目录 274 | File files[] = file.listFiles(); // 声明目录下所有的文件 files[]; 275 | if (files != null) { 276 | for (File childFile : files) { // 遍历目录下所有的文件 277 | deleteFile(childFile); // 把每个文件 用这个方法进行迭代 278 | } 279 | } 280 | } 281 | 282 | //安全删除文件 283 | deleteFileSafely(file); 284 | } 285 | } 286 | 287 | /** 288 | * 重命名 289 | */ 290 | public static File renameFile(File srcFile, String newName) { 291 | 292 | File destFile = new File(newName); 293 | srcFile.renameTo(destFile); 294 | 295 | return destFile; 296 | } 297 | 298 | /** 299 | * 删除所以编辑文件和文件夹 300 | */ 301 | public static void deleteEditFiles() { 302 | try { 303 | File file = new File(getAudioEditStorageDirectory()); 304 | File[] childs = file.listFiles(); 305 | if (childs != null) { 306 | for (File child : childs) { 307 | if (child.isDirectory()) { 308 | deleteFile(child); 309 | } 310 | } 311 | } 312 | } catch (Exception ex) { 313 | ex.printStackTrace(); 314 | } 315 | } 316 | 317 | public static String createNewFileName(File srcFile) { 318 | 319 | final String srcName = srcFile.getName(); 320 | String newName = srcName; 321 | int index = 1; 322 | while (true) { 323 | newName = srcName.substring(0, srcName.lastIndexOf(".")) + "_" + index; 324 | File file = new File(getAudioStorageDirectory(), newName + ".wav"); 325 | if (!file.exists()) { 326 | 327 | break; 328 | } 329 | 330 | index++; 331 | } 332 | 333 | return newName; 334 | } 335 | 336 | public static String createNewFileName(String fileName) { 337 | 338 | String newName = fileName; 339 | int index = 1; 340 | while (true) { 341 | newName = fileName + "_" + index; 342 | File file = new File(getAudioStorageDirectory(), newName + ".wav"); 343 | if (!file.exists()) { 344 | break; 345 | } 346 | 347 | index++; 348 | } 349 | 350 | return newName; 351 | } 352 | 353 | public static boolean checkFileExist(String filePath) { 354 | if (TextUtils.isEmpty(filePath)) { 355 | return false; 356 | } 357 | return new File(filePath).exists(); 358 | } 359 | 360 | public static File checkFileExistFile(long time) { 361 | 362 | File audioFolder = new File(FileUtils.getAudioStorageDirectory()); 363 | File[] files = audioFolder.listFiles(); 364 | if (files != null) { 365 | for (File file : files) { 366 | LogUtil.i("-------------" + file.lastModified() + "," + time); 367 | if (file.lastModified() == time) { 368 | return file; 369 | } 370 | } 371 | } 372 | 373 | return null; 374 | } 375 | 376 | /** 377 | * 安全删除文件.防止删除后重新创建文件,报错 open failed: EBUSY (Device or resource busy) 378 | */ 379 | public static boolean deleteFileSafely(File file) { 380 | if (file != null) { 381 | String tmpPath = file.getParent() + File.separator + System.currentTimeMillis(); 382 | File tmp = new File(tmpPath); 383 | file.renameTo(tmp); 384 | return tmp.delete(); 385 | } 386 | return false; 387 | } 388 | 389 | public static String queryFilePath(Context context, Uri uri) { 390 | 391 | if ("content".equalsIgnoreCase(uri.getScheme())) { 392 | String[] projection = { "_data" }; 393 | Cursor cursor = null; 394 | 395 | try { 396 | cursor = context.getContentResolver().query(uri, projection,null, null, null); 397 | int column_index = cursor.getColumnIndexOrThrow("_data"); 398 | if (cursor.moveToFirst()) { 399 | return cursor.getString(column_index); 400 | } 401 | } catch (Exception e) { 402 | e.printStackTrace(); 403 | } 404 | } 405 | 406 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 407 | return uri.getPath(); 408 | } 409 | 410 | return null; 411 | } 412 | 413 | } 414 | -------------------------------------------------------------------------------- /src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/res/layout/activity_decode.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 |