介绍
最近经常看到一些倒放挑战,非常有意思,所以也想尝试着做一个。音频录制和播放需要用到MediaRecorder
和MediaPlayer
,其中最麻烦的就是如何实现音频倒放。百度了一大圈,搜到的结果都不尽人意
然后在酷安看到了这个 skkily/audioReversal
其中倒放的功能是使用Rxffmpeg
实现的
官方介绍:
RxFFmpeg 是基于 ( FFmpeg 4.0 + X264 + mp3lame + fdk-aac ) 编译的适用于 Android 平台的音视频编辑、视频剪辑的快速处理框架,包含以下功能(视频拼接,转码,压缩,裁剪,片头片尾,分离音视频,变速,添加静态贴纸和gif动态贴纸,添加字幕,添加滤镜,添加背景音乐,加速减速视频,倒放音视频,音频裁剪,##百变魔音##,混音,图片合成视频,视频解码图片等主流特色功能……
这是开源地址:microshow/RxFFmpeg
这么强大的工具虽然有点大材小用,但是他方便啊
使用方式非常简单,安按照文档配置即可,其中倒放的关键命令是:
ffmpeg -i 文件地址 -vf reverse -af areverse -preset superfast 生成文件地址
使用
为了方便使用及管理,我把音频的操作封装成了一个工具类方便使用
package com.wzl.reversalchallenge;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import io.microshow.rxffmpeg.RxFFmpegInvoke;
import io.microshow.rxffmpeg.RxFFmpegSubscriber;
public class MediaUtil {
private MediaRecorder recorder = null;
private MediaPlayer player = null;
private String path;
private Context mContext;
private String fileName;
private String newFileName;
private Date currentDate;
// 创建需要传入Context,用于获取Android外部缓存路径
MediaUtil(Context context) {
mContext = context;
path = mContext.getExternalCacheDir().getAbsolutePath() + "/";
}
// 开始录音
void startRecord() {
// 以时间作为录音文件名
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
currentDate = Calendar.getInstance().getTime();
fileName = dateFormat.format(currentDate) + ".mp3";
recorder = new MediaRecorder();
// 设置音频源为麦克风
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置媒体输出格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
// 设置媒体输出路径
recorder.setOutputFile(path + fileName);
// 设置媒体编码格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
recorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
recorder.start();
}
// 停止录音
void stopRecord() {
recorder.stop();
recorder.release();
recorder = null;
// 生成倒放音频文件
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
// 为了区分源文件,添加_reverse后缀
newFileName = dateFormat.format(currentDate) + "_reverse.mp3";
reverseAudio(fileName, newFileName);
}
// 开始播放音频
void startPlay() {
player = new MediaPlayer();
try {
player.setDataSource(path + newFileName);
player.prepare();
player.start();
} catch (IOException e) {
e.printStackTrace();
}
}
// 停止播放
void stopPlay() {
player.stop();
player.release();
player = null;
}
// 释放资源,可在onStop()中调用
void closeAll() {
if (recorder != null) {
recorder.release();
recorder = null;
}
if (player != null) {
player.release();
player = null;
}
}
// 音频反转
private void reverseAudio(String fileName, String newFileName) {
// 这里拼接要注意空格
String text = "ffmpeg -i " + path + fileName + " -vf reverse -af areverse -preset superfast " + path + newFileName;
String[] commands = text.split(" ");
RxFFmpegInvoke.getInstance().runCommandRxJava(commands).subscribe(new RxFFmpegSubscriber() {
@Override
public void onFinish() {
}
@Override
public void onProgress(int progress, long progressTime) {
}
@Override
public void onCancel() {
}
@Override
public void onError(String message) {
}
});
}
}
功能全部完成以后,只需要在Activity
中调用即可
package com.wzl.reversalchallenge;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import com.wzl.reversalchallenge.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityMainBinding binding;
private MediaUtil mediaUtil;
private String[] permissions = {Manifest.permission.RECORD_AUDIO};
private int REQUEST_RECORD_AUDIO_PERMISSION = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
// 申请权限
checkPermissions();
// 使用了DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mediaUtil = new MediaUtil(this);
initView();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_record:
Toast.makeText(this, "点击 start_record", Toast.LENGTH_SHORT).show();
if (mediaUtil != null) {
mediaUtil.startRecord();
}
break;
case R.id.stop_record:
Toast.makeText(this, "点击 stop_record", Toast.LENGTH_SHORT).show();
if (mediaUtil != null) {
mediaUtil.stopRecord();
}
break;
case R.id.start_play:
Toast.makeText(this, "点击 start_play", Toast.LENGTH_SHORT).show();
if (mediaUtil != null) {
mediaUtil.startPlay();
}
break;
case R.id.stop_play:
Toast.makeText(this, "点击 stop_play", Toast.LENGTH_SHORT).show();
if (mediaUtil != null) {
mediaUtil.stopPlay();
}
break;
}
}
private void initView() {
binding.startRecord.setOnClickListener(this);
binding.stopRecord.setOnClickListener(this);
binding.startPlay.setOnClickListener(this);
binding.stopPlay.setOnClickListener(this);
}
@Override
protected void onStop() {
super.onStop();
// 释放资源
mediaUtil.closeAll();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "权限授权成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "权限授权失败,请重新授权!", Toast.LENGTH_SHORT).show();
}
}
}
// 检查权限是否申请
private void checkPermissions() {
if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION);
}
}
}
至于界面就不贴了,随便写的,只要功能完成了都好说
我把代码放到Github上了,后续可能还会再修改下界面
rianlu/ReversalChallenge
网友评论