专栏首页恩蓝脚本Android实现伴奏录音合成MP3

Android实现伴奏录音合成MP3

本文实例为大家分享了Android实现伴奏录音合成MP3的具体代码,供大家参考,具体内容如下

基本实现思路如下:

1.利用android自带的录音类(AudioRecord)实现录音.

/**
* 播放伴奏
*/
private MediaPlayer player;
/**
* 返回按钮
*/
private ImageView btnBack;
/**
* 切换歌曲
*/
private Button btnSwitchSong;
/**
* 伴唱时长
*/
private TextView tv_recod_time;
/**
* 歌词VIEW
*/
private LyricView lv_lyric;
/**
* 开始录制
*/
private Button btnPlay;
/**
* 标题
*/
private TextView ivTitle;
private boolean canPlay = true;
private boolean isPause = false;
/***
* 背景音乐模式
*/
private BackgroudMusicMode mode = BackgroudMusicMode.Accompany;
/**
* 歌曲id
*/
private String songId;
/**
* 歌曲名称
*/
private String songName;
/**
* 歌手名字
*/
private String singerName;
/**
* 伴奏文件
*/
private File file;
/**
* 是否正在录制
*/
private boolean isStart = false;
/**
* 录音状态
*/
private boolean starting = false;
/**
* 伴奏时间
*/
private int bztimetmp = 0;
/**
* 伴奏时间
*/
private String bztime = "";
/**
* 录制时间
*/
private int recordTimeLength=0;
/**
* 更新伴奏时间
*/
private RecordTask rt = null;
/**
* 录制频率,单位hz.这里的值注意了,写的不好,可能实例化AudioRecord对象的时候,会出错。我开始写成11025就不行。这取决于硬件设备
* 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
*/
private int sampleRateInHz = 44100;
/**
* 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
*/
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
/**
* 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
*/
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
/**
* 调整播放音量
*/
private AudioManager audioManager;
/**
* 最大音量
*/
private int maxVolume = 0;
/**
* 当前音量
*/
private int currentVolume = 0;
/**
* AudioRecord 写入缓冲区大小
*/
protected int m_in_buf_size;
/**
* 录制音频对象
*/
private AudioRecord mRecorder;
/**
* 录入的字节数组
*/
private byte[] m_in_bytes;
/**
* 存放录入字节数组的大小
*/
private LinkedList<byte[]  m_in_q;
/**
* AudioTrack 播放缓冲大小
*/
private int m_out_buf_size;
/**
* 播放音频对象
*/
private AudioTrack mAudioTrack;
/**
* 播放的字节数组
*/
private byte[] m_out_bytes;
/**
* 录制音频线程
*/
private Thread record;
/**
* 播放音频线程
*/
private Thread play;
/**
* 让线程停止的标志
*/
private boolean flag = true;
/**
* 是否启动回声
*/
private boolean room_flag = true;
/***上面有个播放歌词的组件
/***
* 初始化
*/
private void init() {
audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
registerHeadsetPlugReceiver();
ycApplication = (YueChangApplication) getApplication();
coverDao = new CoverDao(getApplicationContext());
Bundle bundle = getIntent().getExtras();
songId = bundle.getString("songId");
songName = bundle.getString("songName");
singerName = bundle.getString("singerName");
if (songId != null) {
// AudioRecord 得到录制最小缓冲区的大小
m_in_buf_size = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
// 实例化播放音频对象
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat,
m_in_buf_size);
// 实例化一个字节数组,长度为最小缓冲区的长度
m_in_bytes = new byte[m_in_buf_size];
// 实例化一个链表,用来存放字节组数
m_in_q = new LinkedList<byte[] ();
// AudioTrack 得到播放最小缓冲区的大小
m_out_buf_size = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
// 实例化播放音频对象
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat,
m_out_buf_size, AudioTrack.MODE_STREAM);
// 实例化一个长度为播放最小缓冲大小的字节数组
m_out_bytes = new byte[m_out_buf_size];
record = new Thread(new recordSound());
//   if(ycApplication.isHeadsetplug()){
//   }else{
//    m_out_trk = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat,
//      m_out_buf_size, AudioTrack.MODE_STREAM);
//   }
}
}
/**
*
* 类描述:录音线程 
*
* @version 1.0
*/
class recordSound implements Runnable {
@Override
public void run() {
// 初始化输出流
DataOutputStream dos = null;
try {
File audioFile = new File(SongUtil.getRecordSingPCMPath(songId));
// 初始化输出流
dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(audioFile)));
byte[] bytes_pkg;
if (mRecorder.getState() == AudioRecord.STATE_UNINITIALIZED) {
// 实例化播放音频对象
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig,
audioFormat, m_in_buf_size);
}
// 开始录音
mRecorder.startRecording();
while (flag) {
int size = mRecorder.read(m_in_bytes, 0, m_in_buf_size);
bytes_pkg = m_in_bytes.clone();
if (m_in_q.size()  = 2) {
m_in_q.removeFirst();
}
m_in_q.add(bytes_pkg);
if ((ycApplication.isHeadsetplug() && ycApplication.isOpenInEarphone())
|| (!ycApplication.isHeadsetplug() && ycApplication.isOpenInSpeaker())) {
//Log.d(SingSingleActivity.this.getClass().getName(), "启动录音播放1");
if (play == null||!room_flag) {
//Log.d(SingSingleActivity.this.getClass().getName(), "启动录音播放2");
room_flag = true;
play = new Thread(new playRecord());
// 启动播放线程
play.start();
}
} else {
if(room_flag||play != null){
//Log.d(SingSingleActivity.this.getClass().getName(), "关闭录音播放1");
room_flag = false;
if (play != null) {
play.interrupt();
}
play = null;
}
}
// 写入PCM文件
dos.write(bytes_pkg, 0, size);
dos.flush();
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
try {
// 关闭录音
if (mRecorder != null) {
try {
if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
// 关闭录音
mRecorder.stop();
mRecorder.release();
}
} catch (Exception e2) {
// TODO: handle exception
}
}
if (dos != null) {
dos.close();
}
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
}
}

2.录音完成后,调用开源工具(Mad)实现PCM合成输出到MP3文件.

主要调用的合成方法:

/***
* 方法描述:本地方法调用JNI合并mp3PCM与sourcePCM
* @param sourcePCM
* @param mp3PCM
* @param mixPCM
* @return
*/
public static native int mix2PCMToPCM(String sourcePCM, String mp3PCM, String mixPCM);
String recordPCMPath = SongUtil.getRecordSingPCMPath(songId); //录音生成的PCM文件
String accompanyPCMPath = SongUtil.getAccompanySongPCMPath(songId); //伴奏解码生成的PCM文件
String mixPCMPath = SongUtil.getMixSingPCMPath(songId); //合成后的PCM文件
String mixMP3Path = SongUtil.getMixSingMp3Path(songId); //合成后的MP3文件
// 混音
int code = SongEncodeUtil.mix2PCMToPCM(recordPCMPath, accompanyPCMPath, mixPCMPath);
if (code == 0) {
// 转换混合后音频格式 TO mp3
int i = SimpleLame.convert(mixPCMPath, mixMP3Path, m_in_buf_size);
Log.i(SingSingleActivity.this.getClass().getName(), "转换" + i + "混音完成");
saveMp3File(mixMP3Path);
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android实现轮询的三种方式

    砸漏
  • Android 裁剪人脸类的实例代码

    以上所述是小编给大家介绍的Android 裁剪人脸的实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对ZaL...

    砸漏
  • Android自定义TipView仿QQ长按后的提示窗口

    通过看书和参考各位大神的博客(再次对大神表示恭敬),我用了一下午时间写完了这么一个view。

    砸漏
  • NullException、Token的作用、Mapstruct用法

    token主要用在会话管理,防止表单提交和防止CSRF攻击,同时token支持跨域访问,无状态,不存储session信息。

    关忆北.
  • 既然Java反射可以访问和修改私有成员变量,那封装成private还有意义么?

    安全是指不让代码被非法看到/访问。但是只要人能拿到代码,总会有办法去查看和改变代码。其他答案提到反射可以用SecurityManager来防止private被访...

    大宽宽
  • 聊聊SimpleCanalConnector的getWithoutAck

    本文主要研究一下SimpleCanalConnector的getWithoutAck

    codecraft
  • RocketMQ学习-NameServer-1

    NameServer在RocketMQ中的角色是配置中心,主要有两个功能:Broker管理、路由管理。因此NameServer上存放的主要信息也包括两类:Bro...

    阿杜
  • Android实现轮询的三种方式

    砸漏
  • 聊聊SimpleCanalConnector的getWithoutAck

    本文主要研究一下SimpleCanalConnector的getWithoutAck

    codecraft
  • Spring OAuth框架下增加用户属性的注意事项

    当我们修改了用户中心的dao、mapper后,需要注意的是,即便鉴权中心没有修改任何东西也一定要重新编译打包,因为修改了公共模块,否则鉴权中心无法识别AppUs...

    算法之名

扫码关注云+社区

领取腾讯云代金券