一、簡介:
前一段時間嘗試錄制了幾集3D編程方面的視頻教程,我發現錄制時最大的障礙是讓腦中的思考、手上的操作和嘴里的解說保持同步,一旦三個“線程”中有一個出錯,就必須停下來重新錄制出錯的部分,同時一心三用也會極大的增加精力消耗,減少有效錄制時間。為了解決這一問題、降低視頻教程的編寫門檻,我嘗試編寫了一個將預先設定的文本插入視頻中作為旁白的小工具,嘗試將思考、操作和解說三個線程分解到三個時間片中運行。
因為以前沒接觸過完整的java窗口程序編寫,這個小工具的代碼非常簡陋生硬,程序名為Saltedfish(中文翻譯咸魚,本文中簡稱為sf)。
二、使用流程:
sf運行包可以從https://pan.baidu.com/s/1i69LBs5下載
1、程序針對安裝了64位java1.7的完整版win7系統編寫,參考網絡上的資料,使用Java語言開發。
2、導出的運行目錄如下:

其中h2是用來存儲項目配置的微型數據庫,sf9_lib是MyEclipse導出的依賴jar包,ffmpeg是視頻處理工具,jacob是java調用com組件工具,mylist是ffmpeg合並音頻流時使用的配置文件,sf9.jar是可執行jar文件,startup是啟動腳本,vlc是視頻播放器。
3、初次使用時需要安裝vlc視頻播放器,安裝目錄沒有限制。
4、啟動h2數據庫:
執行h2-2017-06-10\h2\bin\h2w.bat腳本啟動數據庫,數據庫啟動成功后會使用默認瀏覽器彈出一個數據庫控制台窗口:

使用JDBC URL:jdbc:h2:tcp://127.0.0.1/../../saltedfish訪問已經建立的saltedfish數據庫,用戶名密碼均為saltedfish。點擊連接后可以看到項目表和文件表:

可以看到已經建立了“test”和“test2”兩個測試項目,限於時間,sf程序里並沒有做刪除項目的功能,如果需要刪除項目或文件可以直接在數據庫中操作。
h2數據庫啟動后會在后台運行,關閉控制台不會影響數據庫運行,可以在系統窗口的右下角看到h2數據庫的小圖標。
5、執行startup腳本啟動sf9.jar
startup腳本內容:
java -Dfile.encoding=utf-8 -Xms1024m -Xmx1024m -jar sf9.jar
使用系統默認java,以utf-8編碼執行jar包,為了防止內存不夠,給java堆分配了1GB內存。
6、sf頁面樣式如下:

左上區域用來顯示正在處理的視頻片段,右上區域分為三個選項卡,最右側的選項卡用來對項目進行操作,中間的選項卡對項目中的某一個文件進行操作,左邊的選項卡是根據建議添加的對文件的另一種處理方式,但里面還沒有做任何內容。
點擊項目操作選項卡,在列表里選擇一個項目並打開后,可以看到項目所包含的文件以水平列表的形式顯示在下面,您也可以添加新的項目或者在項目里添加新的文件(目前只支持avi格式):

列表中還顯示了每個文件的文件名和以毫秒數表示的文件長度,點擊“導出目標文件”按鈕可以把列表中的文件合並成一個視頻導出(因為時間限制,這里使用的是最簡單的文件合並方法,只支持編碼完全相同的視頻合並)
7、在列表中選擇一個文件,點擊“處理這一段”按鈕,可以對這個文件進行操作:

類似普通視頻播放器可以對視頻文件進行播放、暫停,拖動滑塊可以調整播放進度。
8、點擊讀文本按鈕,選擇要插入的旁白文本:

點擊播放,程序在播放視頻的同時也會一句接一句的朗讀旁白,在某一時刻點擊某一句旁白所在的按鈕,會重新定位這一句旁白的位置,通過這種方式調整每一句旁白的發音時機(假設視頻的長度總比旁白的長度長)
9、播放完畢或者點擊停止后可以對旁白進行導出:
點擊“srt”按鈕時以srt字幕文件的方式導出旁白文字(限於時間,字幕以默認的utf-8編碼導出,但是實驗發現大部分視頻播放器不支持這種編碼的字幕,需要用戶自己用文本工具將字幕的編碼轉為ANSI),點擊“wav”按鈕時以wav音頻文件的方式導出旁白朗讀音頻和原視頻音頻的疊加音頻,點擊”合並”按鈕則可以把疊加的wav替換到avi里(限於時間,只是導出了一個avi文件,如需要替換請自己替換文件或者修改數據庫)
10、限於時間,只對動態定位旁白語句的部分使用了多線程,最后的導出部分沒有使用多線程,這意味着點擊導出按鈕之后到導出完成之前sf不會響應用戶的任何操作。
三、技術選型與設計流程:
1、選擇java作為操作文件和調度各種工具的語言。
2、選擇前台頁面繪制方法:
Java程序的前台繪制有兩種思路,一是用html*css*js網頁作為前台,通過訪問服務器方式(Ajax,Websocket等)調用后台的java程序工作;二是直接使用java語言編寫桌面應用程序。顯然第一種方式在設計前台頁面時有更多的自由,但是考慮到附帶產生的前后台溝通成本和用戶部署成本以及思維切換成本,選擇使用java直接編寫桌面程序。
常用的Java桌面程序框架有三種:Swing、Awt、Swt,參考網絡上的評價決定使用Swt框架,在實際使用中發現Swt具有以下缺點:
a、無法像html一樣方便的選取元素,除非在程序中用專用的變量保存對象,否則再想找到就很難了。
b、不能像css一樣方便的批量定義和繼承ui樣式,需要對每一個元素單獨設置樣式。
c、只有創建窗口的主線程可以修改ui的狀態,其他線程想要修改ui狀態(比如播放進度條的變化),需要用特殊的語法通知主線程在主線程認為合適的時候進行操作,這一點比JavaScript差距不可以道里計。
(也許這些缺點是因為作者對Swt掌握的還不夠深入,但同樣的時間用來學習html技術能夠產生更好的效果)
3、對文字轉語音工具進行選擇:
常見的文字轉語音方式有兩種,一是通過http等協議調用百度、訊飛等“語音雲”,將語音雲生成的朗讀結果返回給用戶;二是在本地安裝語音生成程序。考慮到離線操作的可能性,使用本地語音生成;考慮到安裝的方便性,使用Window操作系統自帶的語音朗讀工具(SAPI)。
4、思考程序所必須的主干流程,針對每個流程畫出頁面草圖。
5、根據確定的流程設計相應的文件管理辦法
參考一些大型商業軟件設計了兩個版本,發現都需要花費不短的時間開發專門的文件管理模塊,限於時間使用一個微型數據庫h2Database對項目的結構進行保存。
6、根據確定的數據流在網上查詢每個節點的相關知識,先編寫用於實驗的小例程,然后把實驗完畢的小例程整合到主程序中。
四、代碼結構介紹
程序代碼可以在https://github.com/ljzc002/Saltedfish下載
工程結構如下:

1、程序的入口main方法在Win_sf類中,這個類繼承自Swt的窗口類Shell(值得注意的是Shell類默認不可被繼承,所以需要在Win_sf的構造函數中添加一行“checkSubclass();”,並且為它定義一個空方法)。
其中 :
initGui() 方法設定了整個Swt窗口的布局,並在布局的同時設定了按鈕點擊和進度條拖動的事件監聽;
InitAssats()方法初始化了vlc播放器與左上方容器的綁定,並且通過vlc播放器的回調函數修改進度條位置;
Win_sf類的最后是按鈕點擊事件的響應方法,在進行事件響應時,要特別注意響應方法是否由主線程執行,如果不是要使用syncExec(同步)或asyncExec(異步)方法將響應交給主線程執行,如果是,則要注意響應方法會不會引起主線程卡頓。
2、CompositeVideoSurface和SwtEmbeddedMediaPlayer是從github下載的兩個類,它們實現了通過Swt調用vlc播放器的功能。vlc的Java官方示例是使用swing框架實現的,試驗過程中我嘗試了使用Swt調用Awt,再用Awt調用swing的方法,結果播放器有聲音無圖像。
3、Event_cb類里編寫了兩個通過Java反射設置按鈕響應函數的方法,分別是不帶參數和帶參數的版本。
4、Util_File類里包含了文件處理和數據庫操作的相關方法,主要是常規的JDBC實現和Java流操作。
5、Comp_sf類是顯示在窗口下部水平列表里的小容器的類,這個笑容器包含了對應文件的一些信息,並且可以被選中
6、Btn_sf類是窗口右上區域顯示每一列旁白文本的按鈕類。
7、MSTTSSpeech1類是負責通過Jacob調用Windows TTS組件的類,這個類的功能相對完善,包括了對朗讀線程的初始化、開始、停止、釋放四個主要環節的控制,線程之間的關系如下:

在這里語音朗讀由com線程進行,ThreadDemo線程只負責對旁白語句和com線程的調度,ThreadDemo進程的消亡並不會立即令com線程停止,com線程仍會完成它當前的任務或者在接到明確的關閉命令時停止;每次播放暫停,com線程和ThreadDemo線程都會被釋放,繼續播放時將建立新的com線程和ThreadDemo線程。
微軟TTS組件的一個缺點是無法方便的生成“特定長度的沉默”,所以只好生成許多段語音,再把它們拼接起來
對音視頻文件的剪切與合並也在這個類中進行,ffmpeg是一款功能強大的開源視頻處理工具,這里使用JNI技術對其進行調用,使用ffmpeg時遇到的難點主要有兩處:
一是ffmpeg似乎沒有直接的方法能夠把一個流偏移一定長度疊加到另一個流里,所以我只好使用一個迂回的方法:

這個方法會在工作目錄下產生一些中間文件
二是在使用ffmpeg前一定要搞明白要操作的流的編碼格式,然后選用合適的解/編碼器進行解/編碼,否則會導致經過處理后的片段無法合並到一起。
sf用到的部分ffmpeg命令如下:
1 #帶編碼剪切 2 ffmpeg -i 2017FFFHHH-HC720P國語中字.mp4 -vcodec h264 -t 360 -ss 0 ./temp4/fh2h264.avi 3 4 #提取第一個音頻流 5 ffmpeg -y -i fh2.avi -map 0:a:0 tempa.wav 6 7 #提升音頻流的音量和采樣數、聲道數 8 ffmpeg -y -i temptts.wav -af volume=10dB -ar 48000 -ac 2 tempttsl.wav 9 10 #截取音頻流 11 ffmpeg -y -i tempa.wav -t 3 -ss 0 temp1.wav 12 13 ffmpeg -y -i tempa.wav -ss 3 temp2.wav 14 15 #疊加混音 16 ffmpeg -y -i temp1.wav -i tempttsl.wav -filter_complex amix=inputs=2:duration=longest:dropout_transition=0 temp1ttsl.wav 17 18 #這種是文件層面的整合,要求文件的格式必須完全相同 19 ffmpeg -y -i "concat:temp1ttsl.wav|temp2.wav" -c copy tempa.wav 20 #這個是流層面的整合 21 ffmpeg -y -f concat -safe 0 -i mylist.txt -c copy tempa.wav 22 #mylist.txt文件 23 #file temp1ttsl.wav 24 #file temp2.wav 25 26 #最終替換音頻,使用編解碼器時間更長文件更小,使用copy模式則相反 27 ffmpeg -y -i fh2.avi -i bbb.wav -vcodec h264 -map 0:v -map 1 fh2bbb2.avi
8、H2_db類負責新建或關閉與h2數據庫的JDBC連接,這里選用了h2數據庫的TCP連接方式,並且使用了相對路徑進行連接。
9、BarUpdater類是在網上找到的一個進度條更新線程,sf並沒有使用它。
編寫完畢的程序可以使用MyEclipse導出為可執行的jar包,更近一步的,我們也可以使用exe4j工具把jar包打包成一個exe文件,您可以在我的github上找到一個exe4j配置文件來方便您的打包過程(打包時我把sfx.jar放在了sfx_lib文件夾里)
關於打包另一個需要注意的地方是:jar和exe4j導出的exe調用dll和exe的根路徑相同,但可運行jar操作文件的根路徑是jar所在的文件夾,而exe4j導出的exe的操作文件的根路徑則是所使用的jdk的bin目錄!
五、代碼引用:
sf編寫過程中在網絡上進行了數百次搜索,其中引用代碼較多的來源如下:
1、在編寫SwtUI時學習了IBM developerWorks上的Swt教程:
https://www.ibm.com/developerworks/cn/opensource/os-jface2/index.html?ca=drs-
2、在編寫h2數據庫模塊時參考了博客園·孤傲蒼狼博客:
http://www.cnblogs.com/xdp-gacl/p/4171024.html
3、為了在Swt中使用vlc,引用了github上的caprica/vlcj-swt-demo項目,該項目基於GNU GENERAL PUBLIC LICENSE協議發布
https://github.com/caprica/vlcj-swt-demo
4、jacob工具基於GNU LESSER GENERAL PUBLIC LICENSE協議發布,我不確定只是調用這個工具是否需要繼承其開源協議。
可能有一些代碼引用被忽略了,如果您發現被忽略的引用代碼,請聯系我添加它們。
sf引用了兩個使用不同GNU協議的開源工具,所以我也不知道sf應該以何種方式開源,如果還有GNU以外的可授權部分,則以MIT協議開源。
限於時間和水平,sf只實現了我所需要的最簡單的功能,並且存在各種缺陷和bug,如果您需要更多的功能可以自己添加。
