天天看點

FFmpeg在JAVA中的使用以及Process.waitFor()引發的阻塞問題

此文已由作者葉海嘯授權網易雲社群釋出。

歡迎通路網易雲社群,了解更多網易技術産品營運經驗。

FFmpeg是一個開源免費跨平台的視訊和音頻流方案,可以快速對音視訊流進行多方面的處理,本文主要介紹FFmpeg常用的指令與參數講解,如何在JAVA中使用FFmpeg以及遇到的一些問題。

背景

項目需求中涉及到有關于視訊、音頻的一系列處理,包含視訊中音頻提取、視訊首幀提取、音頻重采樣、字幕壓縮的功能,一直在研究ffmpeg,僅僅幾個功能,卻深受ffmpeg的折磨。

今天談談ffmpeg在java中的簡單使用,首先下載下傳ffmpeg包,官方位址:http://ffmpeg.org/download.html, 這裡建議下載下傳Linux Static Builds版本的,輕小而且解壓後可以直接使用,本人使用的版本是ffmpeg-git-20170922-64bit-static.tar.xz。

解壓之後,檔案夾中有一個可執行檔案ffmpeg,在linux上可以直接運作./ffmpeg -version,可以檢視ffmpeg的版本資訊,以及configuration配置資訊。

基本指令

  • 視訊中音頻提取:ffmpeg -i [videofile] -vn -acodec copy [targetaudiofile]
  • 視訊首幀提取:ffmpeg -i [videofile] -vframes 1 -q:v 2 -f image2  [imagefile]
  • 音頻重采樣:ffmpeg -i [audiofile] -ar [samplingrate]  [targetaudiofile]
  • 字幕壓縮:ffmpeg -i [videofile] -vf subtitles=[subtitle.srt] [targetvideofile]

指令說明

  • audiofile、videofile是音視訊源檔案,可以是本地檔案,也可以是網絡檔案URL;
  • 提取音頻流時,-vn 忽略視訊流 -acodec 設定聲音編解碼器,未設定時則使用與輸入流相同的編解碼器,如果需要提取視訊流,則參數變為-an -vcodec;
  • -vframes 表示提取的第幾幀,擷取第一桢則後面的值為1,如果後面的值大于1,那麼最後的[imagefile]不能指定一個檔案,不然會報錯,如下
    FFmpeg在JAVA中的使用以及Process.waitFor()引發的阻塞問題
  • 指定了輸出的檔案名為“1.jpeg”,報錯:不能從1.jpeg檔案中擷取第二幀的檔案名,因為-vframes隻要大于1,則會提取出每一幀的圖檔,建議使用如%03d.jpeg來作為檔案名,那麼它解析的結果便是001.jpeg,002.jpeg,...依次編号往後;
  • -q:v 2 q代表品質quality, v代表視訊流,2是控制品質的參數;-f指定輸出的格式是image2;
  • 除了使用-vframes來擷取視訊幀,還有使用-ss參數來擷取,-ss後的時間參數是自行設定,并且在視訊的有效時間内(格式為00:00:00),使用-ss時,如果沒有使用%03d.jpeg來作為檔案名,則擷取的是-ss參數指定的那個時間的幀;
  • -ar表示使用新的采樣率,常用的有8,000Hz、16,000Hz、22,050Hz、32,000Hz、44,100Hz;
  • subtitle.srt是字幕檔案(中文字幕即把英文變為中文,其它格式一緻),這邊就使用最簡單的srt标準格式,srt檔案寫入的字元編碼需要是UTF-8,否則壓縮的時候會報無法讀取srt檔案;
  • 若想壓縮中文字幕,需要系統中有中文字型,使用fc-list查詢系統支援的字型,fc-list :lang=zh查詢支援的中文字型

JAVA使用遇到的問題

一般需要調用系統指令時,大部分人第一反應肯定是使用Runtime.getRuntime().exec(command)傳回一個process對象,再調用process.waitFor()來等待指令執行結束,擷取執行結果。

産品剛上線,運作很穩定,但是沒過多久,産品同學說從某個時間點開始添加的視訊都不出來了!因為這個視訊必須要經過一系列的處理,才會展示出來,是以中途某個環節出錯了。

首先檢視了日志,沒有發現任何的報錯,但是幸好開發的時候加了debug日志,每一句指令exec前後都會打一句log,于是看下是否“開始執行”和“執行成功”兩句log都列印了,結果發現在截取首幀的時候,隻列印了“開始執行”,一直沒有結束,那麼猜測程序堵塞了。

但是,我把産品同學的視訊拿過來,直接執行提取視訊第一幀的指令,提示圖檔未提取成功,後來發現該視訊是産品同學通過某個壓縮工具壓縮過的,點開視訊可以看見黑屏,看不到任何東西,肯定是壓縮時把視訊壓縮出錯了,但是截取首幀指令既然執行結束了,按道理不應該一直堵塞啊?   

FFmpeg在JAVA中的使用以及Process.waitFor()引發的阻塞問題

于是通過dump下了記憶體鏡像檔案,指令jmap -dump:live,format=b,file=heap.dmp PID,通過jvisualvm工具檢視,發現有很多如下的堆棧:     

FFmpeg在JAVA中的使用以及Process.waitFor()引發的阻塞問題

是以可以判斷,确實是在截取首幀的時候,程序阻塞了,但是為什麼會阻塞???

解決方案

檢視waitFor()源碼可以發現,其實調用的是Object類中的,wait()方法,并且未指定等待時間,那麼如果一直不傳回,則會一直阻塞。

并且檢視了JDK的幫助文檔,如下   

FFmpeg在JAVA中的使用以及Process.waitFor()引發的阻塞問題

是以,可以得出結論:如果外部程式不斷在向标準輸出流(對于jvm來說就是輸入流)和标準錯誤流寫資料,而JVM不讀取的話,當緩沖區滿之後将無法繼續寫入資料,最終造成阻塞在waitFor()這裡。

解決方法:在waitFor()之前,利用單獨兩個線程,分别處理process的getInputStream()和getErrorSteam(),防止緩沖區被撐滿,導緻阻塞;

 /**
 * 處理process輸出流和錯誤流,防止程序阻塞
 * 在process.waitFor();前調用
 * @param process
 */
private static void dealStream(Process process) {
    if (process == null) {
        return;
    }
    // 處理InputStream的線程
    new Thread() {
        @Override
        public void run() {
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = null;
            try {
                while ((line = in.readLine()) != null) {
                    logger.info("output: " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();
    // 處理ErrorStream的線程
    new Thread() {
        @Override
        public void run() {
            BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String line = null;
            try {
                while ((line = err.readLine()) != null) {
                    logger.info("err: " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    err.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();
}      

免費體驗雲安全(易盾)内容安全、驗證碼等服務

更多網易技術、産品、營運經驗分享請點選。

相關文章:

【推薦】 Kubernetes 在網易雲中的落地優化實踐

【推薦】 寓教于樂——玩轉角色互換遊戲