java 阻塞for,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]不能指定一個文件,不然會報錯,如下

b66836d3e62557bb4889781bbf86fd9a.png

指定了輸出的文件名為“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都打印了,結果發現在截取首幀的時候,只打印了“開始執行”,一直沒有結束,那么猜測進程堵塞了。

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

ec87296452dc4dcb8419cdc3862e6078.png

於是通過dump下了內存鏡像文件,命令jmap -dump:live,format=b,file=heap.dmp PID,通過jvisualvm工具查看,發現有很多如下的堆棧:

7cbd1e38aedc81a93fe8d1b58fc1418a.png

因此可以判斷,確實是在截取首幀的時候,進程阻塞了,但是為什么會阻塞???

解決方案

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

並且查看了JDK的幫助文檔,如下

d52f5d8d8ca0e6c82a3aa807ad5520c8.png

因此,可以得出結論:如果外部程序不斷在向標准輸出流(對於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();

}

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值