Android開發(fā)MediaCodec和lamemp3多段音頻截取拼接
思路分析
截取很簡(jiǎn)單,只要用MediaCodec進(jìn)行解碼解出pcm格式的數(shù)據(jù),再把pcm數(shù)據(jù)用MediaCodec進(jìn)行編碼或者用其他第三方的進(jìn)行編碼 。
拼接就比較麻煩,音頻的音質(zhì)會(huì)受到采樣率,比特率和聲道的影響,所以理想的狀態(tài)是這三個(gè)屬性要一樣進(jìn)行拼接才能保證音質(zhì) 。
舉個(gè)栗子,a和b是兩首采樣率,比特率和聲道都不一樣的歌,要拼接成c,首先要設(shè)置c的采樣率,比特率和聲道,這里用a的來進(jìn)行設(shè)置,然后拼接,播放c的時(shí)候會(huì)發(fā)現(xiàn)a部分的音質(zhì)是沒問題的,到了b部分的時(shí)候音質(zhì)就會(huì)出現(xiàn)問題。
解決這個(gè)問題很簡(jiǎn)單,先把a(bǔ)和b的采樣率,比特率和聲道都轉(zhuǎn)成一樣就可以了。對(duì)于音視頻開發(fā)的人來說這個(gè)問題很好解決,就寫個(gè)轉(zhuǎn)換采樣率,比特率和聲道的工具,或者使用 ffmpeg。
通過github找到了幾個(gè),經(jīng)過測(cè)試最后選擇了lamemp3,lamemp3是c語言寫的,怎么編譯網(wǎng)上很多就不說了,好了開始正題 。
首先說說思路,先通過MediaCodec把要處理的幾個(gè)音頻解碼出pcm文件,再把這些pcm文件通過lamemp3轉(zhuǎn)成采樣率,比特率和聲道一樣的mp3,再通過MediaCodec把這些mp3合并成一個(gè)pcm數(shù)據(jù),最后就是把這個(gè)pcm數(shù)據(jù)轉(zhuǎn)成自己想要的格式,可以用MediaCodec轉(zhuǎn)成aac或者用lamemp3再轉(zhuǎn)成mp3。
AudioHolder.java屬性類
記錄音頻的采樣率,比特率,聲道,截取的開始時(shí)間,截取的結(jié)束時(shí)間,路徑和文件名
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調(diào)用lamemp3類
public class SimpleLame { static { System.loadLibrary("native-lib"); } /** * pcm文件轉(zhuǎn)換mp3函數(shù) */ 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; /** * 進(jìn)行解碼和拼接 */ private void encoders(String path,List<AudioHolder> list){ File file = new File(path); if(file.exists()){ file.delete(); } //統(tǒng)一采樣率,比特率和聲道 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); } } //臨時(shí)用的pcm文件 String pcm = Environment.getExternalStorageDirectory().getAbsolutePath()+"/HMSDK/"+System.currentTimeMillis()+".pcm"; List<String> mp3s = new ArrayList<>(); //總時(shí)長(zhǎng),用來計(jì)算進(jìn)度用的 long duration = 0; for (AudioHolder holder :list){ //只有1個(gè)音頻的時(shí)候直接轉(zhuǎn)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文件轉(zhuǎn)成mp3 SimpleLame.convert(this,pcm,mp3 ,holder.getSampleRate(), channelCount,sampleRate,bitRate, 1 ); mp3s.add(mp3); } //只有一個(gè)音頻就完成操作 if(list.size() == 1){ if(encoderListener != null){ encoderListener.onOver(path); } return; } //以下可換成其他代碼,比如用MediaCodec轉(zhuǎn)成aac,因?yàn)椴蓸勇?比特率和聲道都是一樣的文件 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轉(zhuǎn)成pcm文件返回轉(zhuǎn)換數(shù)據(jù)的大小 total += encoderMP3(mp3,pcmos,total,duration); } try { pcmos.flush(); pcmos.close(); } catch (IOException e) { e.printStackTrace(); } //把pcm文件轉(zhuǎn)成mp3 SimpleLame.convert(this,pcm,path ,sampleRate, channelCount,sampleRate,bitRate, 1 ); if(encoderListener != null){ encoderListener.onOver(path); } } /** * 進(jìn)行解碼 */ 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(); } //這段音頻的時(shí)長(zhǎng) 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) { //判斷解碼出來的數(shù)據(jù)是否在截取的范圍內(nèi) 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(); } //進(jìn)度條 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); //超過截取時(shí)間結(jié)束解碼 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轉(zhuǎn)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回調(diào)的進(jìn)度條函數(shù),進(jìn)度條以解碼占50,pcm轉(zhuǎn)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這個(gè)文件夾自己創(chuàng)建或改成自己的,我都是保存在手機(jī)內(nèi)是為了方便測(cè)試
GitHub: https://github.com/a422070876/AudioMerge
以上就是Android開發(fā)MediaCodec和lamemp3多段音頻截取拼接的詳細(xì)內(nèi)容,更多關(guān)于MediaCodec和lamemp3音頻截取拼接的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android仿微信和QQ多圖合并框架(類似群頭像)的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android仿微信和QQ多圖合并框架的相關(guān)資料,其實(shí)就是我們平時(shí)所見的群聊頭像,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12Android Studio輕松構(gòu)建自定義模板的步驟記錄
這篇文章主要給大家介紹了關(guān)于Android Studio輕松構(gòu)建自定義模板的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10Android自定義View onDraw()方法會(huì)調(diào)用兩次的問題解決
這篇文章主要介紹了Android自定義View onDraw()方法會(huì)調(diào)用兩次的問題解決,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Android 操作系統(tǒng)獲取Root權(quán)限 原理詳細(xì)解析
許多機(jī)友新購(gòu)來的Android機(jī)器沒有破解過Root權(quán)限,無法使用一些需要高權(quán)限的軟件,以及進(jìn)行一些高權(quán)限的操作,其實(shí)破解手機(jī)Root權(quán)限是比較簡(jiǎn)單及安全的,破解Root權(quán)限的原理就是在手機(jī)的/system/bin/或/system/xbin/目錄下放置一個(gè)可執(zhí)行文件“su”2013-10-10Android使用Intent顯示實(shí)現(xiàn)頁面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Android使用Intent顯示實(shí)現(xiàn)頁面跳轉(zhuǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08android Web跳轉(zhuǎn)到app指定頁面并傳遞參數(shù)實(shí)例
這篇文章主要介紹了android Web跳轉(zhuǎn)到app指定頁面并傳遞參數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03