android 使用MediaCodec對多段音訊進行擷取和拼接
剪下很簡單,只要用MediaCodec進行解碼解出pcm格式的資料,再把pcm資料用MediaCodec進行編碼或者用其他第三方的進行編碼 合併就比較麻煩,音訊的音質會受到取樣率,位元率和聲道的影響,所以理想的狀態是這三個屬性要一樣進行合成才能保證音質 舉個栗子,a和b是兩首取樣率,位元率和聲道都不一樣的歌,要合併成c,首先要設定c的取樣率,位元率和聲道,這裡用a的來進行設定,然後合併,播放c的時候會發現a部分的音質是沒問題的,到了b部分的時候音質就會出現問題 解決這個問題很簡單,先把a和b的取樣率,位元率和聲道都轉成一樣就可以了。對於音視訊開發的人來說這個問題很好解決,有能力的就寫個轉換取樣率,位元率和聲道的工具,沒能力的就用ffmpeg,像我這樣的沒能力的,只能用現成的了,看過我以前文章就知道我不想往app內加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這個資料夾自己建立或改成自己的,我都是儲存在手機內是為了方便測試