android采用FFmpeg實(shí)現(xiàn)音視頻合成與分離
上一篇文章談到音頻剪切、混音、拼接與轉(zhuǎn)碼,也詳細(xì)介紹cMake配置與涉及FFmpeg文件的導(dǎo)入: android端采用FFmpeg進(jìn)行音頻混合與拼接剪切 ?,F(xiàn)在接著探討音視頻的合成與分離。
1、音頻提取
從多媒體文件中提取音頻,關(guān)鍵命令為“-acodec copy -vn”,其中“-acodec copy”是采用音頻編碼器拷貝音頻流,“-vn”是去掉video視頻流:
/** * 使用ffmpeg命令行進(jìn)行抽取音頻 * @param srcFile 原文件 * @param targetFile 目標(biāo)文件 * @return 抽取后的音頻文件 */ public static String[] extractAudio(String srcFile, String targetFile){ //-vn:video not String mixAudioCmd = "ffmpeg -i %s -acodec copy -vn %s"; mixAudioCmd = String.format(mixAudioCmd, srcFile, targetFile); return mixAudioCmd.split(" ");//以空格分割為字符串?dāng)?shù)組 }
2、視頻提取
從多媒體文件中提取視頻,關(guān)鍵命令為“-vcodec copy -an”,其中“-vcodec copy”是采用視頻編碼器拷貝視頻流,“-an”是去掉audio音頻流:
/** * 使用ffmpeg命令行進(jìn)行抽取視頻 * @param srcFile 原文件 * @param targetFile 目標(biāo)文件 * @return 抽取后的視頻文件 */ public static String[] extractVideo(String srcFile, String targetFile){ //-an audio not String mixAudioCmd = "ffmpeg -i %s -vcodec copy -an %s"; mixAudioCmd = String.format(mixAudioCmd, srcFile, targetFile); return mixAudioCmd.split(" ");//以空格分割為字符串?dāng)?shù)組 }
3、音視頻合成
把音頻和視頻文件合成多媒體文件,關(guān)鍵命令是“-i %s -i %s -t”,分別代表輸入音頻、視頻和文件時(shí)長(zhǎng)。需要注意的是,如果原視頻文件包含有音頻,先把單獨(dú)視頻流抽取出來,然后再使用獨(dú)立音頻和視頻進(jìn)行合成:
/** * 使用ffmpeg命令行進(jìn)行音視頻合成 * @param videoFile 視頻文件 * @param audioFile 音頻文件 * @param duration 視頻時(shí)長(zhǎng) * @param muxFile 目標(biāo)文件 * @return 合成后的文件 */ @SuppressLint("DefaultLocale") public static String[] mediaMux(String videoFile, String audioFile, int duration, String muxFile){ //-t:時(shí)長(zhǎng) 如果忽略音視頻時(shí)長(zhǎng),則把"-t %d"去掉 String mixAudioCmd = "ffmpeg -i %s -i %s -t %d %s"; mixAudioCmd = String.format(mixAudioCmd, videoFile, audioFile, duration, muxFile); return mixAudioCmd.split(" ");//以空格分割為字符串?dāng)?shù)組 }
單獨(dú)的視頻提取出來后,進(jìn)行音視頻合成:
public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what == 100){ String audioFile = PATH + File.separator + "tiger.mp3";//tiger.mp3 String muxFile = PATH + File.separator + "media-mux.mp4"; try { //使用MediaPlayer獲取視頻時(shí)長(zhǎng) MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(videoFile); mediaPlayer.prepare(); //單位為ms int videoDuration = mediaPlayer.getDuration()/1000; Log.i(TAG, "videoDuration=" + videoDuration); mediaPlayer.release(); //使用MediaMetadataRetriever獲取音頻時(shí)長(zhǎng) MediaMetadataRetriever mediaRetriever = new MediaMetadataRetriever(); mediaRetriever.setDataSource(audioFile); //單位為ms String duration = mediaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); int audioDuration = (int)(Long.parseLong(duration)/1000); Log.i(TAG, "audioDuration=" + audioDuration); mediaRetriever.release(); //如果視頻時(shí)長(zhǎng)比音頻長(zhǎng),采用音頻時(shí)長(zhǎng),否則用視頻時(shí)長(zhǎng) int mDuration = Math.min(audioDuration, videoDuration); //使用純視頻與音頻進(jìn)行合成 String[] commandLine = FFmpegUtil.mediaMux(temp, audioFile, mDuration, muxFile); executeFFmpegCmd(commandLine); isMux = false; } catch (Exception e) { e.printStackTrace(); } } }
拼接好FFmpeg命令后,調(diào)用native方法去執(zhí)行:
/** * 調(diào)用ffmpeg處理音視頻 * @param handleType handleType */ private void doHandleMedia(int handleType){ String[] commandLine = null; switch (handleType){ case 0://音視頻合成 try { //視頻文件有音頻,先把純視頻文件抽取出來 commandLine = FFmpegUtil.extractVideo(videoFile, temp); isMux = true; } catch (Exception e) { e.printStackTrace(); } break; case 1://提取音頻 String extractAudio = PATH + File.separator + "extractAudio.aac"; commandLine = FFmpegUtil.extractAudio(srcFile, extractAudio); break; case 2://提取視頻 String extractVideo = PATH + File.separator + "extractVideo.mp4"; commandLine = FFmpegUtil.extractVideo(srcFile, extractVideo); break; default: break; } executeFFmpegCmd(commandLine); } FFmpeg執(zhí)行的回調(diào): /** * 執(zhí)行ffmpeg命令行 * @param commandLine commandLine */ private void executeFFmpegCmd(final String[] commandLine){ if(commandLine == null){ return; } FFmpegCmd.execute(commandLine, new FFmpegCmd.OnHandleListener() { @Override public void onBegin() { Log.i(TAG, "handle media onBegin..."); } @Override public void onEnd(int result) { Log.i(TAG, "handle media onEnd..."); if(isMux){ mHandler.obtainMessage(100).sendToTarget(); }else { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MediaHandleActivity.this, "handle media finish...", Toast.LENGTH_SHORT).show(); } }); } } }); }
好了,使用FFmpeg進(jìn)行音視頻合成與分離介紹完畢。如果各位有什么問題或者建議,歡迎交流。
源碼:鏈接地址。如果對(duì)您有幫助,麻煩fork和star。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android學(xué)習(xí)項(xiàng)目之簡(jiǎn)易版微信為例(二)
這篇文章主要以簡(jiǎn)易版微信為例,實(shí)現(xiàn)簡(jiǎn)易版微信的登陸、注冊(cè)界面的編寫與簡(jiǎn)單交互,感興趣的小伙伴們可以參考一下2016-06-06Android微信右滑退出功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android微信右滑退出功能的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01Flutter如何通過一行命令解決多個(gè)pubspec.yaml文件的依賴項(xiàng)問題
這篇文章主要介紹了Flutter如何通過一行命令解決多個(gè)pubspec.yaml文件的依賴項(xiàng)問題,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06android push推送相關(guān)基本問答總結(jié)
現(xiàn)在網(wǎng)上一大堆的關(guān)于推送方面的實(shí)現(xiàn)原理:1.通過pull(拉),也就是通過客戶端主動(dòng)定時(shí)輪詢服務(wù)器請(qǐng)求數(shù)據(jù)。2.通過push(推),服務(wù)器通過一個(gè)長(zhǎng)連接主動(dòng)推送消息到客戶端。這兩個(gè)方式都可以實(shí)現(xiàn)推送功能。pull這個(gè)方式?jīng)]什么問題好理解。2015-05-05Android實(shí)現(xiàn)拍照或者選取本地圖片
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)拍照或者選取本地圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Android實(shí)現(xiàn)從底部彈出的Dialog的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)從底部彈出的Dialog的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值 ,需要的朋友可以參考下2018-04-04Android開發(fā)使用RecyclerView添加點(diǎn)擊事件實(shí)例詳解
這篇文章主要為大家介紹了Android開發(fā)使用RecyclerView添加點(diǎn)擊事件實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Android實(shí)現(xiàn)手勢(shì)密碼功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手勢(shì)密碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12