Android开发MediaCodec和lamemp3多段音频截取拼接
作者:Android社区
思路分析
截取很简单,只要用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
以上就是Android开发MediaCodec和lamemp3多段音频截取拼接的详细内容,更多关于MediaCodec和lamemp3音频截取拼接的资料请关注脚本之家其它相关文章!