package com.frank.ffmpeg.activity import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.os.Environment import android.os.Handler import android.os.Message import android.os.Bundle import android.util.Log import android.view.View import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import java.io.File import java.util.ArrayList import java.util.Locale import com.frank.ffmpeg.R import com.frank.ffmpeg.adapter.WaterfallAdapter import com.frank.ffmpeg.handler.FFmpegHandler import com.frank.ffmpeg.util.FFmpegUtil import com.frank.ffmpeg.util.FileUtil import com.frank.ffmpeg.handler.FFmpegHandler.MSG_INFO import com.frank.ffmpeg.handler.FFmpegHandler.MSG_BEGIN import com.frank.ffmpeg.handler.FFmpegHandler.MSG_FINISH import com.frank.ffmpeg.handler.FFmpegHandler.MSG_PROGRESS import com.frank.ffmpeg.listener.OnItemClickListener import java.lang.StringBuilder /** * Using ffmpeg command to handle audio * Created by frank on 2018/1/23. */ class AudioHandleActivity : BaseActivity() { private val appendFile = PATH + File.separator + "heart.m4a" private var layoutAudioHandle: RecyclerView? = null private var layoutProgress: LinearLayout? = null private var txtProgress: TextView? = null private var currentPosition: Int = 0 private var ffmpegHandler: FFmpegHandler? = null private val outputPath1 = PATH + File.separator + "output1.mp3" private val outputPath2 = PATH + File.separator + "output2.mp3" private var isJointing = false private var infoBuilder: StringBuilder? = null @SuppressLint("HandlerLeak") private val mHandler = object : Handler() { override fun handleMessage(msg: Message) { super.handleMessage(msg) when (msg.what) { MSG_BEGIN -> { layoutProgress!!.visibility = View.VISIBLE layoutAudioHandle!!.visibility = View.GONE } MSG_FINISH -> { layoutProgress!!.visibility = View.GONE layoutAudioHandle!!.visibility = View.VISIBLE if (isJointing) { isJointing = false FileUtil.deleteFile(outputPath1) FileUtil.deleteFile(outputPath2) } if (infoBuilder != null) { Toast.makeText(this@AudioHandleActivity, infoBuilder.toString(), Toast.LENGTH_LONG).show() infoBuilder = null } } MSG_PROGRESS -> { val progress = msg.arg1 val duration = msg.arg2 if (progress > 0) { txtProgress!!.visibility = View.VISIBLE txtProgress!!.text = String.format(Locale.getDefault(), "%d%%", progress) } else { txtProgress!!.visibility = View.GONE } } MSG_INFO -> { if (infoBuilder == null) infoBuilder = StringBuilder() infoBuilder?.append(msg.obj) } else -> { } } } } override val layoutId: Int get() = R.layout.activity_audio_handle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initView() ffmpegHandler = FFmpegHandler(mHandler) } private fun initView() { layoutProgress = getView(R.id.layout_progress) txtProgress = getView(R.id.txt_progress) val list = listOf( getString(R.string.audio_transform), getString(R.string.audio_cut), getString(R.string.audio_concat), getString(R.string.audio_mix), getString(R.string.audio_play), getString(R.string.audio_speed), getString(R.string.audio_echo), getString(R.string.audio_tremolo), getString(R.string.audio_denoise), getString(R.string.audio_add_equalizer), getString(R.string.audio_silence), getString(R.string.audio_volume), getString(R.string.audio_encode), getString(R.string.pcm_concat)) layoutAudioHandle = findViewById(R.id.list_audio_item) val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) layoutAudioHandle?.layoutManager = layoutManager val adapter = WaterfallAdapter(list) adapter.setOnItemClickListener(object : OnItemClickListener { override fun onItemClick(position: Int) { currentPosition = position selectFile() } }) layoutAudioHandle?.adapter = adapter } override fun onViewClick(view: View) { } override fun onSelectedFile(filePath: String) { doHandleAudio(filePath) } /** * Using ffmpeg cmd to handle audio * * @param srcFile srcFile */ private fun doHandleAudio(srcFile: String) { var commandLine: Array? = null if (!FileUtil.checkFileExist(srcFile)) { return } if (!FileUtil.isAudio(srcFile)) { showToast(getString(R.string.wrong_audio_format)) return } when (currentPosition) { 0 -> if (useFFmpeg) { //use FFmpeg to transform val transformFile = PATH + File.separator + "transformAudio.mp3" commandLine = FFmpegUtil.transformAudio(srcFile, transformFile) } else { //use MediaCodec and libmp3lame to transform Thread { val transformPath = PATH + File.separator + "transformAudio.mp3" try { mHandler.obtainMessage(MSG_BEGIN).sendToTarget() val clazz = Class.forName("com.frank.mp3.Mp3Converter") val instance = clazz.newInstance() val method = clazz.getDeclaredMethod("convertToMp3", String::class.java, String::class.java) method.invoke(instance, srcFile, transformPath) mHandler.obtainMessage(MSG_FINISH).sendToTarget() } catch (e: Exception) { Log.e("AudioHandleActivity", "convert mp3 error=" + e.message) } }.start() } 1 //cut audio, it's best not include special characters -> { val suffix = FileUtil.getFileSuffix(srcFile) if (suffix == null || suffix.isEmpty()) { return } val cutFile = PATH + File.separator + "cutAudio" + suffix commandLine = FFmpegUtil.cutAudio(srcFile, 10.5f, 15.0f, cutFile) } 2 //concat audio -> { if (!FileUtil.checkFileExist(appendFile)) { return } concatAudio(srcFile) return } 3 //mix audio -> { if (!FileUtil.checkFileExist(appendFile)) { return } val mixSuffix = FileUtil.getFileSuffix(srcFile) if (mixSuffix == null || mixSuffix.isEmpty()) { return } commandLine = if (mixAudio) { val mixAudio = PATH + File.separator + "mix" + mixSuffix FFmpegUtil.mixAudio(srcFile, appendFile, mixAudio) } else { val mergeAudio = PATH + File.separator + "merge" + mixSuffix FFmpegUtil.mergeAudio(srcFile, appendFile, mergeAudio) } } 4 //use AudioTrack to play audio -> { val audioIntent = Intent(this@AudioHandleActivity, AudioPlayActivity::class.java) audioIntent.data = Uri.parse(srcFile) startActivity(audioIntent) return } 5 //change audio speed -> { val speed = 2.0f // funny effect, range from 0.5 to 100.0 val speedPath = PATH + File.separator + "speed.mp3" commandLine = FFmpegUtil.changeAudioSpeed(srcFile, speedPath, speed) } 6 // setting echo effect -> { val echo = 1000 // echo effect, range from 0 to 90000 val echoPath = PATH + File.separator + "echo.mp3" commandLine = FFmpegUtil.audioEcho(srcFile, echo, echoPath) } 7 //tremolo effect -> { val frequency = 5 // range from 0.1 to 20000.0 val depth = 0.9f // range from 0 to 1 val tremoloPath = PATH + File.separator + "tremolo.mp3" commandLine = FFmpegUtil.audioTremolo(srcFile, frequency, depth, tremoloPath) } 8 //audio denoise -> { val noisePath = PATH + File.separator + "denoise.mp3" commandLine = FFmpegUtil.audioDenoise(srcFile, noisePath) } 9 // equalizer plus -> { // key:band value:gain=[0-20] val bandList = arrayListOf() bandList.add("6b=5") bandList.add("8b=4") bandList.add("10b=3") bandList.add("12b=2") bandList.add("14b=1") bandList.add("16b=0") val equalizePath = PATH + File.separator + "equalize.mp3" commandLine = FFmpegUtil.audioEqualizer(srcFile, bandList, equalizePath) } 10 //silence detect -> { commandLine = FFmpegUtil.audioSilenceDetect(srcFile) } 11 // modify volume -> { val volume = 0.5f // 0.0-1.0 val volumePath = PATH + File.separator + "volume.mp3" commandLine = FFmpegUtil.audioVolume(srcFile, volume, volumePath) } 12 //audio encode -> { val pcmFile = PATH + File.separator + "raw.pcm" val wavFile = PATH + File.separator + "convert.mp3" //sample rate, normal is 8000/16000/44100 val sampleRate = 44100 //channel num of pcm val channel = 2 commandLine = FFmpegUtil.encodeAudio(pcmFile, wavFile, sampleRate, channel) } 13 //concat PCM streams -> { val srcPCM = PATH + File.separator + "audio.pcm" val appendPCM = PATH + File.separator + "audio.pcm" val concatPCM = PATH + File.separator + "concat.pcm" if (!FileUtil.checkFileExist(srcPCM) || !FileUtil.checkFileExist(appendPCM)) { return } mHandler.obtainMessage(MSG_BEGIN).sendToTarget() FileUtil.concatFile(srcPCM, appendPCM, concatPCM) mHandler.obtainMessage(MSG_FINISH).sendToTarget() return } else -> { } } if (ffmpegHandler != null && commandLine != null) { ffmpegHandler!!.executeFFmpegCmd(commandLine) } } private fun concatAudio(selectedPath: String) { if (ffmpegHandler == null || selectedPath.isEmpty() || appendFile.isEmpty()) { return } isJointing = true val targetPath = PATH + File.separator + "concatAudio.mp3" val transformCmd1 = FFmpegUtil.transformAudio(selectedPath, "libmp3lame", outputPath1) val transformCmd2 = FFmpegUtil.transformAudio(appendFile, "libmp3lame", outputPath2) val fileList = ArrayList() fileList.add(outputPath1) fileList.add(outputPath2) val jointVideoCmd = FFmpegUtil.concatAudio(fileList, targetPath) val commandList = ArrayList>() commandList.add(transformCmd1) commandList.add(transformCmd2) commandList.add(jointVideoCmd) ffmpegHandler!!.executeFFmpegCmds(commandList) } override fun onDestroy() { super.onDestroy() mHandler.removeCallbacksAndMessages(null) } companion object { private val PATH = Environment.getExternalStorageDirectory().path private const val useFFmpeg = true private const val mixAudio = true } }