截取很简单,只要用MediaCodec进行解码解出pcm格式的数据,再把pcm数据用MediaCodec进行编码或者用其他第三方的进行编码 。
拼接就比较麻烦,音频的音质会受到采样率,比特率和声道的影响,所以理想的状态是这三个属性要一样进行拼接才能保证音质 。
举个栗子,a和b是两首采样率,比特率和声道都不一样的歌,要拼接成c,首先要设置c的采样率,比特率和声道,这里用a的来进行设置,然后拼接,播放c的时候会发现a部分的音质是没问题的,到了b部分的时候音质就会出现问题。
解决这个问题很简单,先把a和b的采样率,比特率和声道都转成一样就可以了。对于音视频开发的人来说这个问题很好解决,就写个转换采样率,比特率和声道的工具,或者使用 ffmpeg。
通过github找到了几个,经过测试最后选择了lamemp3,lamemp3是c语言写的,怎么编译网上很多就不说了,好了开始正题 。
首先说说思路,先通过MediaCodec把要处理的几个音频解码出pcm文件,再把这些pcm文件通过lamemp3转成采样率,比特率和声道一样的mp3,再通过MediaCodec把这些mp3合并成一个pcm数据,最后就是把这个pcm数据转成自己想要的格式,可以用MediaCodec转成aac或者用lamemp3再转成mp3。
AudioHolder.java属性类,记录音频的采样率,比特率,声道,截取的开始时间,截取的结束时间,路径和文件名
public class AudioHolder {
private String file;
private String name;
private double start;
private double end;
private int sampleRate;
private int channelCount;
private int bitRate;
private String mp3;
public void setMp3(String mp3) {
this.mp3 = mp3;
}
public String getMp3() {
return mp3;
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getStart() {
return start;
}
public void setStart(double start) {
this.start = start;
}
public double getEnd() {
return end;
}
public void setEnd(double end) {
this.end = end;
}
public int getSampleRate() {
return sampleRate;
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public int getChannelCount() {
return channelCount;
}
public void setChannelCount(int channelCount) {
this.channelCount = channelCount;
}
public int getBitRate() {
return bitRate;
}
public void setBitRate(int bitRate) {
this.bitRate = bitRate;
}
}
SimpleLame.java调用lamemp3类
public class SimpleLame {
static {
System.loadLibrary("native-lib");
}
/**
* pcm文件转换mp3函数
*/
public static native void convert(AudioEncoder encoder,String jwav, String jmp3,
int inSampleRate, int outChannel, int outSampleRate, int outBitrate,
int quality);
}
native-lib.cpp
#include <jni.h>
#include <string>
#include "lamemp3/lame.h"
#include <sys/stat.h>
#define INBUFSIZE 4096
#define MP3BUFSIZE (int) (1.25 * INBUFSIZE) + 7200
extern "C"
JNIEXPORT void JNICALL
Java_com_hyq_hm_audiomerge_lame_SimpleLame_convert(JNIEnv *env, jclass type, jobject encoder,
jstring jwav_,jstring jmp3_,
jint inSampleRate,jint outChannel,
jint outSampleRate,jint outBitrate,
jint quality) {
const char *jwav = env->GetStringUTFChars(jwav_, 0);
const char *jmp3 = env->GetStringUTFChars(jmp3_, 0);
// TODO
short int wav_buffer[INBUFSIZE*outChannel];
unsigned char mp3_buffer[MP3BUFSIZE];
// 获取文件大小
struct stat st;
stat(jwav, &st );
jclass cls = env->GetObjectClass(encoder);
jmethodID mid = env->GetMethodID(cls, "setProgress", "(JJ)V");
FILE* fwav = fopen(jwav,"rb");
FILE* fmp3 = fopen(jmp3,"wb");
lame_t lameConvert = lame_init();
lame_set_in_samplerate(lameConvert , inSampleRate);
lame_set_out_samplerate(lameConvert, outSampleRate);
lame_set_num_channels(lameConvert,outChannel);
// lame_set_VBR(lameConvert,vbr_mtrh);
// lame_set_VBR_mean_bitrate_kbps(lameConvert,outBitrate);
lame_set_brate(lameConvert,outBitrate);
lame_set_quality(lameConvert, quality);
lame_init_params(lameConvert);
int read ; int write;
long total=0;
do{
read = (int) fread(wav_buffer, sizeof(short int) * outChannel, INBUFSIZE, fwav);
total += read* sizeof(short int)*outChannel;
env->CallVoidMethod(encoder,mid,(long)st.st_size,total);
if(read!=0){
if (outChannel == 2){
write = lame_encode_buffer_interleaved(lameConvert,wav_buffer,read,mp3_buffer,MP3BUFSIZE);
}else{
write = lame_encode_buffer(lameConvert,wav_buffer,wav_buffer,read,mp3_buffer,MP3BUFSIZE);
}
} else{
write = lame_encode_flush(lameConvert,mp3_buffer,MP3BUFSIZE);
}
fwrite(mp3_buffer, sizeof(unsigned char), (size_t) write, fmp3);
}while (read!=0);
lame_mp3_tags_fid(lameConvert,fmp3);
lame_close(lameConvert);
fclose(fwav);
fclose(fmp3);
env->ReleaseStringUTFChars(jwav_, jwav);
env->ReleaseStringUTFChars(jmp3_, jmp3);
}
AudioMerge.java拼接操作类
public class AudioMerge {
private static final String AUDIO = "audio/";
private Handler audioHandler;
private HandlerThread audioThread;
public AudioMerge(){
audioThread = new HandlerThread("AudioMerge");
audioThread.start();
audioHandler = new Handler(audioThread.getLooper());
}
private OnAudioEncoderListener encoderListener;
public void setEncoderListener(OnAudioEncoderListener encoderListener) {
this.encoderListener = encoderListener;
}
public void start(final String path, final List<AudioHolder> list){
audioHandler.post(new Runnable() {
@Override
public void run() {
encoders(path,list);
}
});
}
public void start(final String path, final List<AudioHolder> list,OnAudioEncoderListener encoderListener){
this.encoderListener = encoderListener;
start(path,list);
}
private static int[] SampleRates = {48000,44100,32000,24000,22050,16000,12000,11025,8000};
private static int[] Mpeg1BitRates = {320,256,224,192,160,128,112,96,80,64,56,48,40,32};
private static int[] Mpeg2BitRates = {160,144,128,112,96,80,64,56,48,40,32,24,16,8};
private static int[] Mpeg25BitRates = {64,56,48,40,32,24,16,8};
private int audioTrackIndex;
private AudioHolder decoderHolder = null;
/**
* 进行解码和拼接
*/
private void encoders(String path,List<AudioHolder> list){
File file = new File(path);
if(file.exists()){
file.delete();
}
//统一采样率,比特率和声道
int bitRate = list.get(0).getBitRate();
int sampleRate = list.get(0).getSampleRate();
int channelCount = list.get(0).getChannelCount();
if(list.size() != 1){
for (AudioHolder holder:list){
bitRate = Math.min(bitRate,holder.getBitRate());
sampleRate = Math.min(sampleRate,holder.getSampleRate());
channelCount = Math.min(channelCount,holder.getChannelCount());
}
sampleRate = format(sampleRate,SampleRates);
if(sampleRate >= SampleRates[2]){
bitRate = format(bitRate,Mpeg1BitRates);
}else if(sampleRate <= SampleRates[6]){
bitRate = format(bitRate,Mpeg25BitRates);
}else{
bitRate = format(bitRate,Mpeg2BitRates);
}
}
//临时用的pcm文件
String pcm = Environment.getExternalStorageDirectory().getAbsolutePath()+"/HMSDK/"+System.currentTimeMillis()+".pcm";
List<String> mp3s = new ArrayList<>();
//总时长,用来计算进度用的
long duration = 0;
for (AudioHolder holder :list){
//只有1个音频的时候直接转mp3
String mp3;
if(list.size() == 1){
mp3 = path;
decoderHolder = null;
}else{
decoderHolder = holder;
mp3 = Environment.getExternalStorageDirectory().getAbsolutePath()+"/HMSDK/"+System.currentTimeMillis()+".mp3";
}
//将音频解码成pcm文件
duration += decoderPCM(holder,pcm);
//把pcm文件转成mp3
SimpleLame.convert(this,pcm,mp3
,holder.getSampleRate(),
channelCount,sampleRate,bitRate,
1
);
mp3s.add(mp3);
}
//只有一个音频就完成操作
if(list.size() == 1){
if(encoderListener != null){
encoderListener.onOver(path);
}
return;
}
//以下可换成其他代码,比如用MediaCodec转成aac,因为采样率,比特率和声道都是一样的文件
decoderHolder = null;
File f = new File(pcm);
if(f.exists()){
f.delete();
}
OutputStream pcmos = null;
try {
pcmos = new FileOutputStream(pcm);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//文件总大小
long total = 0;
for (String mp3 : mp3s){
//将mp3转成pcm文件返回转换数据的大小
total += encoderMP3(mp3,pcmos,total,duration);
}
try {
pcmos.flush();
pcmos.close();
} catch (IOException e) {
e.printStackTrace();
}
//把pcm文件转成mp3
SimpleLame.convert(this,pcm,path
,sampleRate,
channelCount,sampleRate,bitRate,
1
);
if(encoderListener != null){
encoderListener.onOver(path);
}
}
/**
* 进行解码
*/
private long decoderPCM(AudioHolder holder,String pcm){
long startTime = (long) (holder.getStart()*1000*1000);
long endTime = (long) (holder.getEnd()*1000*1000);
//初始化MediaExtractor和MediaCodec
MediaExtractor audioExtractor = new MediaExtractor();
MediaCodec audioDecoder = null;
try {
audioExtractor.setDataSource(holder.getFile());
for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
MediaFormat format = audioExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if(mime.startsWith(AUDIO)){
audioExtractor.selectTrack(i);
audioTrackIndex = i;
if(startTime != 0){
audioExtractor.seekTo(startTime,audioTrackIndex);
}
audioDecoder = MediaCodec.createDecoderByType(mime);
audioDecoder.configure(format, null, null, 0);
audioDecoder.start();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
File f = new File(pcm);
if(f.exists()){
f.delete();
}
//pcm文件
OutputStream pcmos = null;
try {
pcmos = new FileOutputStream(f);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//这段音频的时长
long duration = endTime - startTime;
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (true) {
extractorInputBuffer(audioExtractor, audioDecoder);
int outIndex = audioDecoder.dequeueOutputBuffer(info, 50000);
if (outIndex >= 0) {
ByteBuffer data = audioDecoder.getOutputBuffer(outIndex);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
info.size = 0;
}
if (info.size != 0) {
//判断解码出来的数据是否在截取的范围内
if(info.presentationTimeUs >= startTime && info.presentationTimeUs <= endTime){
byte[] bytes = new byte[data.remaining()];
data.get(bytes,0,bytes.length);
data.clear();
//写入pcm文件
try {
pcmos.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
//进度条
if(encoderListener != null){
int progress = (int) (((info.presentationTimeUs - startTime)*50)/duration);
if(decoderHolder == null){
encoderListener.onEncoder(progress);
}else{
encoderListener.onDecoder(decoderHolder,progress);
}
}
}
}
audioDecoder.releaseOutputBuffer(outIndex, false);
//超过截取时间结束解码
if(info.presentationTimeUs >= endTime){
break;
}
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
try {
pcmos.flush();
pcmos.close();
} catch (IOException e) {
e.printStackTrace();
}
audioDecoder.stop();
audioDecoder.release();
audioExtractor.release();
return duration;
}
/**
* mp3转pcm
*/
private long encoderMP3(String mp3,OutputStream pcmos,long startTime,long duration){
long d = 0;
MediaExtractor audioExtractor = new MediaExtractor();
MediaCodec audioDecoder = null;
try {
audioExtractor.setDataSource(mp3);
for (int i = 0; i < audioExtractor.getTrackCount(); i++) {
MediaFormat format = audioExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if(mime.startsWith(AUDIO)){
d = format.getLong(MediaFormat.KEY_DURATION);
audioExtractor.selectTrack(i);
audioDecoder = MediaCodec.createDecoderByType(mime);
audioDecoder.configure(format, null, null, 0);
audioDecoder.start();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (true) {
extractorInputBuffer(audioExtractor, audioDecoder);
int outIndex = audioDecoder.dequeueOutputBuffer(info, 50000);
if (outIndex >= 0) {
ByteBuffer data = audioDecoder.getOutputBuffer(outIndex);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
info.size = 0;
}
if (info.size != 0) {
byte[] bytes = new byte[data.remaining()];
data.get(bytes,0,bytes.length);
data.clear();
try {
pcmos.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
if(encoderListener != null){
int progress = (int) (((info.presentationTimeUs + startTime)*50)/duration);
encoderListener.onEncoder(progress);
}
}
audioDecoder.releaseOutputBuffer(outIndex, false);
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
audioDecoder.stop();
audioDecoder.release();
audioExtractor.release();
return d;
}
private void extractorInputBuffer(MediaExtractor mediaExtractor, MediaCodec mediaCodec) {
int inputIndex = mediaCodec.dequeueInputBuffer(50000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputIndex);
long sampleTime = mediaExtractor.getSampleTime();
int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
if (mediaExtractor.advance()) {
mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, sampleTime, 0);
} else {
if (sampleSize > 0) {
mediaCodec.queueInputBuffer(inputIndex, 0, sampleSize, sampleTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
mediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
}
}
private int format(int f,int[] fs){
if(f >= fs[0]){
return fs[0];
}else if(f <= fs[fs.length - 1]){
return fs[fs.length - 1];
}else{
for (int i = 1; i < fs.length;i++){
if(f >= fs[i]){
return fs[i];
}
}
}
return -1;
}
/**
* jni回调的进度条函数,进度条以解码占50,pcm转mp3占50
*/
public void setProgress(long size,long total){
if(encoderListener != null){
int progress = 50 + (int) ((total*50)/size);
if(decoderHolder == null){
encoderListener.onEncoder(progress);
}else{
encoderListener.onDecoder(decoderHolder,progress);
}
}
}
public interface OnAudioEncoderListener{
void onDecoder(AudioHolder decoderHolder,int progress);
void onEncoder(int progress);
void onOver(String path);
}
}
用的时候
private AudioMerge audioMerge = new AudioMerge();
private List<AudioHolder> list = new ArrayList<>();
audioMerge.start(Environment.getExternalStorageDirectory().getAbsolutePath()+"/HMSDK/test_merge.mp3",list);
还有HMSDK这个文件夹自己创建或改成自己的,我都是保存在手机内是为了方便测试
GitHub: https://github.com/a422070876/AudioMerge
————————————————
Copyright© 2013-2019