PS:源代碼以及在線演示在文章的末尾
簡介
架構
系統前臺顯示採用了Div + CSS+ Javascript技術。其中前臺顯示用了一些Javascript插件,例如說生成旋轉燈籠式效果的CloudCarousel,生成導航菜單的ddsmoothmenu,用於表單驗證的jQuery Validation Engine等等。其中視頻播放器用了FlowPlayer(包含了RTMP插件)來支持HTTP的點播和RTMP的直播。按理說視頻文件上傳的時候可以使用Ajax實現,但是考慮工作比較繁瑣,所以暫時還是使用直接文件上傳的方式。
系統的後臺使用了JavaEE系統中最傳統的三層架構:Action層,Service層以及DAO層。其中Action層使用了Struts2框架,用於處理前臺頁面傳來的請求;DAO層使用了Hibernate框架,用於和數據庫的交互。Spring框架則用於將Action層,Service層以及DAO層整合起來。系統的前後臺交互主要使用了JSTL標籤和EL表達式。此外考慮到國際化方面的要求,採用了Struts2的i18n方式,將所有頁面上的文字都抽取坐來保存到單獨的文件中,這樣就可以實現多種語言的顯示。
系統開始運行後,會開啓兩個線程VideoThumbnailThread和VideoTranscoderThread。VideoThumbnailThread用於截取視頻的縮略圖,VideoTranscoderThread則用於轉碼上傳的視頻。這兩個線程都是通過調用ffmpeg.exe完成相應的功能。
此外,系統中還包含了查看媒體信息的功能。該功能通過調用MediaInfo.dll完成,不再詳述。
整個系統的框架如下圖所示,後文中再詳細記錄每部分的功能。效果
系統主要包含了以下幾個頁面:主頁(index.jsp):進入系統的第一個頁面。
視頻列表(videolist.jsp):列表顯示視頻資源的頁面。按照視頻源的類型的不同可以分爲點播(VOD)視頻列表和直播(Live)視頻列表。
視頻編輯(videoedit.jsp):編輯視頻資源的頁面。按照視頻源的類型的不同可以分爲點播(VOD)編輯和直播(Live)編輯。按照操作的不同可以分爲添加(Add)和編輯(Edit)。
視頻內容(videocontent.jsp):查看視頻的內容的頁面。按照視頻源的類型的不同可以分爲點播(VOD)視頻內容和直播(Live)視頻內容。
設置(configure.jsp):設置頁面。用於配製系統的參數。
關於(about.jsp):顯示系統相關的一些信息的頁面。
首頁
網站首頁的如下圖所示。Logo位於左上角,菜單欄位於右上角。爲了美觀一些,頁面上方做了一個燈箱效果的特效,隨機顯示一些視頻的縮略圖。頁面下方則分別列出“點播列表”和“直播列表”中最近添加的幾個視頻。
點播列表
點播列表頁面如下圖所示。在該頁面中,可以對點播視頻進行簡單的管理:查看內容,添加,修改以及刪除。直播列表
點播列表頁面如下圖所示。直播列表實質上使用了和點播列表相同的頁面。在該頁面中,可以對直播視頻進行簡單的管理:查看內容,添加,修改以及刪除。視頻內容頁面
視頻內容頁面如下所示。該頁面中包含了一個FlowPlayer播放器,該播放器用於播放視頻內容。點播視頻內容頁面如下所示。
直播視頻內容頁面如下所示。儘管表面上看點播和直播頁面完全相似,但是實際上點播和直播的機制是不一樣的。點播依靠的是HTTP漸進式下載,而直播依靠的是FlowPlayer的RTMP組件。
視頻添加頁面
點播視頻添加頁面如下所示。該頁面用於上傳一個點播視頻。“名稱”輸入框用於指定視頻的名稱,“文件”用於指定上傳的視頻文件,“簡介”可以指定針對該視頻的一段簡介。其中,“名稱”和“文件”兩個輸入框爲必填項,否則無法提交該頁面的表單。直播視頻添加頁面如下所示。從圖中可以看出直播和點播的添加頁面很類似。它們的不同在於:點播是上傳一個視頻文件,而直播是指定一個URL(目前該URL只支持RTMP)。和點播類似,“名稱”和“URL”兩個輸入框爲必填項,否則無法提交該頁面的表單。
視頻編輯頁面
點播視頻編輯頁面如下所示。該頁面用於編輯已經完成上傳的點播視頻的信息。
直播視頻編輯頁面如下所示。該頁面用於編輯直播視頻的信息。
配置
系統的配置頁面如下圖所示。該頁面用於配置系統的各種設置(以轉碼設置爲主)。每次配置完成後重啓系統後設置生效。包含以下參數。- 視頻編碼器:轉碼使用的視頻編碼器,默認值爲“libx264”。
- 視頻碼率 (bps) :視頻編碼的碼率(單位爲bps),默認值爲500000(500kbps)。
- 視頻幀率 (fps) :視頻編碼的幀率(單位爲fps),默認值爲25。
- 音頻編碼器:轉碼時候使用的音頻編碼器,默認值爲“libmp3lame”。
- 音頻採樣率 (Hz) :音頻編碼的採樣率(單位爲Hz),默認值爲22050(22.05kHz)。
- 音頻碼率 (bps) :音頻編碼的碼率(單位爲bps),默認值爲64000(64kbps)。
- 視頻寬 (pixel) :轉碼後視頻的寬(單位爲pixel),默認值爲640。
- 視頻高 (pixel) :轉碼後視頻的高(單位爲pixel),默認值爲360。
- 使用水印:選擇轉碼後的視頻是否加水印。如果該選項選擇“是”,則會在轉碼後的視頻中添加水印。
- 水印文件路徑:水印文件的路徑,此處爲文件相對於網站根目錄的路徑,默認爲“watermark/svw.png”。
- 水印位置-x座標 (pixel) :水印在視頻中的位置——x座標(單位爲pixel),默認值爲5。
- 水印座標-y座標 (pixel) :水印在視頻中的位置——y座標(單位爲pixel),默認值爲5。
- 保持寬高比並且填充黑邊:選擇轉碼後是否保持寬高比。如果該選項選擇“是”,則轉碼後的視頻會保持寬高比,並且當輸入和輸出視頻寬高比不一樣的時候,會在輸出視頻中添加黑邊。如果該選項選擇“否”,則轉碼後的視頻會拉伸成輸出分辨率。
- 輸出視頻封裝格式:輸出視頻的封裝格式,默認爲“flv”。
- 視頻截圖位置 (sec) :獲取視頻縮略圖的位置(單位爲sec),默認爲5sec。該默認值的含義是獲取視頻第5秒處的視頻幀作爲視頻的縮略圖。
- 上傳視頻存放文件夾:用於存放上傳的視頻文件,默認值爲“videoori”。
- 轉碼視頻存放文件夾:用於存放轉碼的視頻文件,默認值爲“video”。
- 視頻截圖存放文件夾:用於存放視頻的截圖,默認值爲“videothumbnail”。
MediaInfo信息
MediaInfo信息位於視頻內容頁面中,在默認的情況下是不顯示的。通過修改網頁中代碼可以顯示視頻的MediaInfo信息。轉碼前文件信息如下圖所示,從圖中可以看出轉碼前的文件格式是MPEG-PS,視頻編碼爲MPEG-1,音頻編碼爲MP2。
轉碼後文件信息如下圖所示,從圖中可以看出轉碼前的文件是FLV格式的,視頻編碼爲H.264,音頻編碼MP3。
多國語言
本系統支持多國語言。選擇右上角菜單的“語言”->“English”可以將系統切換到英文。英文界面如下圖所示。英文版的視頻點播列表如下圖所示。
英文版的視頻內容頁面如下圖所示。
英文版的配置界面如下圖所示。
網站部分
下面簡單記錄一下網站部分的關鍵源代碼。分成三個部分:數據庫、前臺和後臺。
數據庫
系統包含了4張表:video,videostate,category,configure。下面簡單記錄每個表的含義。
Video表
Video表用於存儲視頻記錄。每一個視頻對應Video表中的一條記錄。該表中的字段如下表所示。
名稱 | 類型 | 鍵 | 簡介 |
id | int | 主鍵 | 標識 |
name | varchar |
| 名稱 |
intro | varchar |
| 簡介 |
edittime | datetime |
| 編輯時間 |
categoryid | int | 外鍵 | 所屬類別 |
islive | int |
| 是否是直播 |
url | varchar |
| 處理後視頻URL |
oriurl | varchar |
| 上傳視頻URL(點播) |
thumbnailurl | varchar |
| 縮略圖URL |
videostateid | int | 外鍵 | 狀態 |
remark | varchar |
| 備註 |
*其中點播視頻的URL爲視頻文件的相對路徑。
下面例舉Video表中的幾項和URL有關數據(受限於篇幅,省略其它字段)。
Id | name | islive | url | Oriurl | Thumbnailurl |
1 | Avatar | 0 | video/1.flv | videoori/Avatar Blueray 3D.flv | videothumbnail/1.jpg |
2 | 建國大業 | 0 | video/2.flv | videoori/建國大業.mpg | videothumbnail/2.jpg |
3 | Sintel | 0 | video/3.flv | videoori/sintel.wmv | videothumbnail/3.jpg |
4 | Warcraft III | 0 | video/4.flv | videoori/Warcraft3_End.avi | videothumbnail/4.jpg |
5 | CUC IESchool | 0 | video/5.flv | videoori/cuc_ieschool.mkv | videothumbnail/5.jpg |
6 | 中國合夥人 | 0 | video/6.flv | videoori/中國合夥人.flv | videothumbnail/6.jpg |
7 | 那些年,我們一起追的女孩 | 0 | video/7.flv | videoori/那些年,我們一起追的女孩.mp4 | videothumbnail/7.jpg |
8 | 春晚是什麼? | 0 | video/8.flv | videoori/春晚是什麼?.mov | videothumbnail/8.jpg |
9 | Forrest Gump IMAX | 0 | video/9.flv | videoori/Forrest Gump IMAX.mp4 | videothumbnail/9.jpg |
10 | 屌絲男士 | 0 | video/10.flv | videoori/屌絲男士.mov | videothumbnail/10.jpg |
11 | Titanic | 0 | video/11.flv | videoori/Titanic.mkv | videothumbnail/11.jpg |
12 | 北廣傳媒移動電視 | 1 | rtmp://www.bj-mobiletv.com:8000/live/live1 |
| videothumbnail/12.jpg |
13 | 香港電視臺 | 1 | rtmp://live.hkstv.hk.lxdns.com/live/hks |
| videothumbnail/13.jpg |
14 | 東莞電視臺 | 1 | rtmp://ftv.sun0769.com/dgrtv1/mp4:b1 |
| videothumbnail/14.jpg |
15 | 看看新聞網 | 1 | rtmp://live.kksmg.com:80/live/mp4:Stream_1 |
| videothumbnail/15.jpg |
16 | 紹興新聞綜合 | 1 | rtmp://www.scbtv.cn/live/new |
| videothumbnail/16.jpg |
17 | 央廣購物 | 1 | rtmp://wx.cnrmall.com/live/flv |
| videothumbnail/17.jpg |
19 | 亞太衛視 | 1 | rtmp://58.61.150.198:1935/live/Livestream |
| videothumbnail/19.jpg |
20 | CCTV中學生 | 1 | rtmp://ams.studytv.cn/livepkgr/264 |
| videothumbnail/20.jpg |
22 | 廣州綜合 | 1 | rtmp://116.199.115.228/live/gztv_tv |
| videothumbnail/22.jpg |
23 | 睛彩廣州 | 1 | rtmp://116.199.115.228/live/cmmb |
| videothumbnail/23.jpg |
24 | 深圳娛樂 | 1 | rtmp://tv.sznews.com:1935/live/live_233_mc43 |
| videothumbnail/24.jpg |
25 | 南陽新聞綜合 | 1 | rtmp://61.136.113.35/tslsChannelLive/zyys888/live |
| videothumbnail/25.jpg |
26 | 大慶綜合 | 1 | rtmp://live1.baihuwang.com:1935/live/zh |
| videothumbnail/26.jpg |
27 | 溫州新聞綜合 | 1 | rtmp://livetv.dhtv.cn:1935/live/news |
| videothumbnail/27.jpg |
Videostate表
Videostate表用於存儲視頻的狀態。該表中字段如下表所示。
名稱 | 類型 | 鍵 | 簡介 |
id | int | 主鍵 | 標識 |
name | varchar |
| 名稱 |
order | int |
| 順序 |
cssstyle | varchar |
| CSS樣式 |
remark | varchar |
| 備註 |
目前按照執行的順序定義了4種狀態:等待上傳,等待截圖,等待轉碼,完成。其中“CSS樣式”用於輔助顯示視頻的狀態。該表的內容如下
id | name | order | cssstyle | remark |
1 | 等待上傳 | 1 | background:#CCFFFF |
|
2 | 等待截圖 | 2 | background:#00FF99 |
|
3 | 等待轉碼 | 3 | background:#00FF00 |
|
4 | 完成 | 4 | background:#FFFFFF |
|
Category表
Category表用於存儲視頻的分類。該表可以用於確定視頻的分類,目前還沒有做太多開發。
名稱 | 類型 | 鍵 | 簡介 |
id | int | 主鍵 | 標識 |
name | varchar |
| 名稱 |
parentid | int |
| 父類別 |
remark | varchar |
| 備註 |
Configure表
Configure表用於存儲系統配置信息。該表中字段如下表所示。
名稱 | 類型 | 鍵 | 簡介 |
id | int | 主鍵 | 標識 |
name | varchar |
| 名稱 |
val | varchar |
| 值 |
remark | varchar |
| 備註 |
該表的內容如下。
Id | name | val | remark |
1 | transcoder_vcodec | libx264 | Video encoder |
2 | transcoder_bv | 500000 | Video bitrate |
3 | transcoder_framerate | 25 | Video frame rate |
4 | transcoder_acodec | libmp3lame | Audio encoder |
5 | transcoder_ar | 22050 | Audio sample rate |
6 | transcoder_ba | 64000 | Audio bitrate |
7 | transcoder_scale_w | 640 | Video width |
8 | transcoder_scale_h | 360 | Video height |
9 | transcoder_watermarkuse | true | Use Watermark |
10 | transcoder_watermark_url | watermark/svw.png | Watermark file’s URL |
11 | transcoder_watermark_x | 5 | Watermark’s location (x) |
12 | transcoder_watermark_y | 5 | Watermark’s location (y) |
13 | transcoder_keepaspectratio | true | Keep original aspect ratio |
14 | transcoder_outfmt | flv | Output file format |
15 | thumbnail_ss | 5 | When to get thumbnail (from start) |
16 | folder_videoori | videoori | Folder to save Upload Video |
17 | folder_video | video | Folder to save Transcode Video |
18 | folder_thumbnail | videothumbnail | Folder to save Thumbnail of Video |
前臺
前臺部分沒有太多可以記錄的,基本上都是各種DIV和CSS的調整。其中使用了較多的JSTL標籤以及EL表達式。此外使用了Struts2標籤用於支持國際化。基本的jsp頁面只有6個:
index.jsp:首頁。除了基本頁面之外,還有一些輔助頁面:
videolist.jsp:視頻列表——直播視頻列表,點播視頻列表。
videoedit.jsp:視頻編輯——直播視頻添加,直播視頻編輯,點播視頻添加,點播視頻編輯。
videocontent.jsp:視頻內容——直播視頻內容,點播視頻內容。
configure.jsp:配置頁面。
about.jsp:關於頁面。
cfooter.jsp:頁腳。
cheader.jsp:標題欄。
csidebar_recent.jsp:側邊欄(最近添加)。
error.jsp:錯誤頁面。
FlowPlayer播放器
前臺頁面中用於視頻播放的播放器是FlowPlayer。FlowPlayer播放器安裝有3個步驟:(1)包含FlowPlayer的Js文件
<script type="text/javascript" src="videoplayer/flowplayer-3.2.8.min.js"></script>
(2)添加一個<a>標記- <a href=“http://www.mywebsite.com/myVideo.flv”
- style=“display:block;width:425px;height:300px;”
- id=“player”>
- </a>
<a href="http://www.mywebsite.com/myVideo.flv"
style="display:block;width:425px;height:300px;"
id="player">
</a>
(3)添加一段Javascript代碼,其中要包含FlowPlayer的Swf文件<script language="JavaScript">
flowplayer("player", "path/to/the/flowplayer-3.2.18.swf");
</script>
FlowPlayer通過HTTP播放點播視頻文件的源代碼如下所示。{video.url}中保存了視頻的相對URL。<br><div class="dp-highlighter bg_html"><div class="bar"><div class="tools"><b>[html]</b> <a href="#" class="ViewSource" title="view plain" onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span class="tracking-ad" data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 495px; top: 17186px; width: 16px; height: 16px; z-index: 99;"><embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="16" height="16" name="ZeroClipboardMovie_4" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=4&width=16&height=16" wmode="transparent"></div></span><span class="tracking-ad" data-mod="popu_169"> <a href="#" class="PrintSource" title="print" onclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" onclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-xml"><li class="alt"><span><span class="tag"><</span><span class="tag-name">a</span><span> </span><span class="attribute">id</span><span>=</span><span class="attribute-value">"player"</span><span> </span><span class="attribute">href</span><span>=</span><span class="attribute-value">" {pageContext.request.scheme}://
<a id="player" href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/${video.url}">
</a>
<script>
flowplayer("player", "videoplayer/flowplayer-3.2.8.swf");
</script>
FlowPlayer通過RTMP播放直播視頻文件的示例代碼如下所示。{video.url}中保存了視頻的URL。播放RTMP的時候用到了FlowPlayer的RTMP Plugin。需要注意的是,RTMP的URL需要拆分開來分別填到不同的位置。在這裏通過split(‘/’)函數按照“/”將字符串分離爲字符串數組。然後將“protocol://server/app”填至plugins的netConnectionUrl字段,playpath填至clip的url字段。<br><div class="dp-highlighter bg_html"><div class="bar"><div class="tools"><b>[html]</b> <a href="#" class="ViewSource" title="view plain" onclick="dp.sh.Toolbar.Command('ViewSource',this);return false;">view plain</a><span class="tracking-ad" data-mod="popu_168"> <a href="#" class="CopyToClipboard" title="copy" onclick="dp.sh.Toolbar.Command('CopyToClipboard',this);return false;">copy</a><div style="position: absolute; left: 495px; top: 17480px; width: 16px; height: 16px; z-index: 99;"><embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="16" height="16" name="ZeroClipboardMovie_5" align="middle" allowscriptaccess="always" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="id=5&width=16&height=16" wmode="transparent"></div></span><span class="tracking-ad" data-mod="popu_169"> <a href="#" class="PrintSource" title="print" onclick="dp.sh.Toolbar.Command('PrintSource',this);return false;">print</a></span><a href="#" class="About" title="?" onclick="dp.sh.Toolbar.Command('About',this);return false;">?</a></div></div><ol start="1" class="dp-xml"><li class="alt"><span><span> </span><span class="tag"><</span><span class="tag-name">a</span><span> </span><span class="attribute">id</span><span>=</span><span class="attribute-value">"player"</span><span class="tag">></span><span> </span></span></li><li class=""><span> <span class="tag"></</span><span class="tag-name">a</span><span class="tag">></span><span> </span></span></li><li class="alt"><span><span class="comments"><!-- Parse RTMP URL --></span><span> </span></span></li><li class=""><span><span class="tag"><</span><span class="tag-name">script</span><span class="tag">></span><span> </span></span></li><li class="alt"><span> <span class="attribute">str</span><span>=</span><span class="attribute-value">' {video.url}’;
<a id="player">
</a>
<!-- Parse RTMP URL -->
<script>
str='${video.url}';
arr=str.split('/');
//rtmp://server/app/playpath
protocol=arr[0];
server=arr[2];
app=arr[3];
playpath=arr[4];
flowplayer("player", "videoplayer/flowplayer-3.2.8.swf",{
clip: {
//scaling:'orig',
url: playpath,
provider: 'rtmp',
live:'true'
},
plugins: {
rtmp: {
url: 'videoplayer/flowplayer.rtmp-3.2.8.swf',
netConnectionUrl: protocol+'//'+server+'/'+ app
}
}
});
</script>
後臺
後臺部分採用了JavaEE的SSH (Struts2 + Spring + Hibernate)的三層架構,其中的代碼不再詳細記錄。三層分別爲Action層、Service層和DAO層。其中Action層採用了Struts2框架,DAO層採用了Hibernate框架,而Spring則整合了這三個層面。
BaseService/BaseDAO
SSH三層架構雖然在解耦合方面做得很好,但是卻導致開發起來非常繁瑣,嚴重降低了開發的速度。爲了方便起見,本系統中在Service層編寫了BaseService用於完成通用的Service操作;在DAO層也編寫了BaseDAO用於完成通用的DAO操作。這樣除非有特殊需求,一般情況下只要調用BaseService中的方法就可以完成各種基本的操作。BaseService包含的接口如下所示。
- /**
- * 最簡單的視頻網站
- * Simplest Video Website
- *
- * 雷霄驊 Lei Xiaohua
- *
- * [email protected]
- * 中國傳媒大學/數字電視技術
- * Communication University of China / Digital TV Technology
- * http://blog.csdn.net/leixiaohua1020
- *
- * 本程序是一個最簡單的視頻網站視頻。它支持
- * 1.直播
- * 2.點播
- * This software is the simplest video website.
- * It support:
- * 1. live broadcast
- * 2. VOD
- */
- package service;
- import java.util.List;
- /**
- * @author 雷霄驊
- * 對Object的Service
- * 包含了一些通用的方法
- */
- public interface BaseService {
- /**
- * 保存一個對象
- * @param object 一個對象
- */
- public void save(Object object);
- /**
- * 更新一個對象
- * @param object 一個對象
- */
- public void update(Object object);
- /**
- * 刪除一個對象
- * @param object 一個對象
- */
- public void delete(Object object);
- /**
- * 根據ID讀取一個指定名稱的對象
- * @param targetName 對象的名稱
- * @param id 對象的ID
- * @return 一個對象
- */
- public Object ReadByID(String targetName,int id);
- @SuppressWarnings(“rawtypes”)
- /**
- * 獲取指定類型的所有對象
- * @param targetName 對象類型名稱
- * @return 對象的列表
- */
- public List ReadAll(String targetName);
- @SuppressWarnings(“rawtypes”)
- /**
- * 根據“屬性-值”獲取多個指定類型的對象
- * @param targetName 對象類型名稱
- * @param propertyName 對象中屬性的名稱
- * @param propertyValue 對象中屬性的值
- * @return 對象的列表
- */
- public List ReadByProperty(String targetName,String propertyName,Object propertyValue);
- /**
- * 根據“屬性-值”獲取一個指定類型的對象
- * @param targetName 對象類型名稱
- * @param propertyName 對象中屬性的名稱
- * @param propertyValue 對象中屬性的值
- * @return 一個對象
- */
- public Object ReadSingle(String targetName,String propertyName,Object propertyValue);
- /**
- * 獲取多個指定類型的對象,可以限定獲取對象數目的多少,並且根據特定的屬性進行排序。
- * @param targetName 對象類型名稱
- * @param propertyName 對象中屬性的名稱,用於排序
- * @param num 結果對象列表的最大數目
- * @param order 排序方式,可以選擇“asc”或者“desc”
- * @return 對象的列表
- */
- public List ReadLimitedByOrder(String targetName,String propertyName,int num,String order);
- /**
- * 獲取指定類型的對象的數量。
- * @param targetName 對象類型名稱
- * @return 數量
- */
- public int ReadCount(String targetName);
- /**
- * 根據“屬性-值”爲條件,獲取指定類型的對象的數量。
- * @param targetName 對象類型名稱
- * @param propertyName 對象中屬性的名稱
- * @param propertyValue 對象中屬性的值
- * @return 數量
- */
- public int ReadCountByProperty(final String targetName,String propertyName, Object value);
- /**
- * 兩個功能:
- * 1.根據“屬性-值”獲取多個指定類型的對象
- * 2.限定獲取對象數目的多少,並且根據特定的屬性進行排序。
- * @param targetName 對象類型名稱
- * @param readpropertyName 對象中屬性的名稱,用於獲取對象
- * @param readvalue 對象中屬性的值
- * @param orderpropertyName 對象中屬性的名稱,用於排序
- * @param num 結果對象列表的最大數目
- * @param order 排序方式,可以選擇“asc”或者“desc”
- * @return
- */
- public List ReadByPropertyAndLimitedByOrder(final String targetName, final String readpropertyName,
- final Object readvalue,final String orderpropertyName, final int num, final String order);
- }
/**
* 最簡單的視頻網站
* Simplest Video Website
*
* 雷霄驊 Lei Xiaohua
*
* [email protected]
* 中國傳媒大學/數字電視技術
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序是一個最簡單的視頻網站視頻。它支持
* 1.直播
* 2.點播
* This software is the simplest video website.
* It support:
* 1. live broadcast
* 2. VOD
*/
package service;
import java.util.List;
/**
* @author 雷霄驊
* 對Object的Service
* 包含了一些通用的方法
*/
public interface BaseService {
/**
* 保存一個對象
* @param object 一個對象
*/
public void save(Object object);
/**
* 更新一個對象
* @param object 一個對象
*/
public void update(Object object);
/**
* 刪除一個對象
* @param object 一個對象
*/
public void delete(Object object);
/**
* 根據ID讀取一個指定名稱的對象
* @param targetName 對象的名稱
* @param id 對象的ID
* @return 一個對象
*/
public Object ReadByID(String targetName,int id);
@SuppressWarnings("rawtypes")
/**
* 獲取指定類型的所有對象
* @param targetName 對象類型名稱
* @return 對象的列表
*/
public List ReadAll(String targetName);
@SuppressWarnings("rawtypes")
/**
* 根據“屬性-值”獲取多個指定類型的對象
* @param targetName 對象類型名稱
* @param propertyName 對象中屬性的名稱
* @param propertyValue 對象中屬性的值
* @return 對象的列表
*/
public List ReadByProperty(String targetName,String propertyName,Object propertyValue);
/**
* 根據“屬性-值”獲取一個指定類型的對象
* @param targetName 對象類型名稱
* @param propertyName 對象中屬性的名稱
* @param propertyValue 對象中屬性的值
* @return 一個對象
*/
public Object ReadSingle(String targetName,String propertyName,Object propertyValue);
/**
* 獲取多個指定類型的對象,可以限定獲取對象數目的多少,並且根據特定的屬性進行排序。
* @param targetName 對象類型名稱
* @param propertyName 對象中屬性的名稱,用於排序
* @param num 結果對象列表的最大數目
* @param order 排序方式,可以選擇“asc”或者“desc”
* @return 對象的列表
*/
public List ReadLimitedByOrder(String targetName,String propertyName,int num,String order);
/**
* 獲取指定類型的對象的數量。
* @param targetName 對象類型名稱
* @return 數量
*/
public int ReadCount(String targetName);
/**
* 根據“屬性-值”爲條件,獲取指定類型的對象的數量。
* @param targetName 對象類型名稱
* @param propertyName 對象中屬性的名稱
* @param propertyValue 對象中屬性的值
* @return 數量
*/
public int ReadCountByProperty(final String targetName,String propertyName, Object value);
/**
* 兩個功能:
* 1.根據“屬性-值”獲取多個指定類型的對象
* 2.限定獲取對象數目的多少,並且根據特定的屬性進行排序。
* @param targetName 對象類型名稱
* @param readpropertyName 對象中屬性的名稱,用於獲取對象
* @param readvalue 對象中屬性的值
* @param orderpropertyName 對象中屬性的名稱,用於排序
* @param num 結果對象列表的最大數目
* @param order 排序方式,可以選擇“asc”或者“desc”
* @return
*/
public List ReadByPropertyAndLimitedByOrder(final String targetName, final String readpropertyName,
final Object readvalue,final String orderpropertyName, final int num, final String order);
}
FFmpeg部分
截取縮略圖線程和轉碼線程
系統在啓動後會開啓兩個線程:VideoThumbnailThread和VideoTranscoderThread。其中VideoThumbnailThread線程會不停的檢測需要截取縮略圖的視頻,調用相應的FFmpeg命令截取縮略圖;VideoTranscoderThread線程會不停的監測需要轉碼的視頻,調用相應的FFmpeg命令轉碼視頻。何以確定視頻是否需要截取縮略圖以及是否需要轉碼?這是通過video表中的videostateid字段來標識的。視頻開始上傳後,該視頻記錄會被標記爲“等待上傳”(第1步);上傳完畢後,該視頻記錄會被標記爲“等待截圖”(第2步);截圖完畢後,該視頻記錄會被標記爲“等待轉碼”(第3步);轉碼完畢後,該視頻記錄會被標記爲“完成”(第4步)。VideoThumbnailThread會不停地檢查系統中“等待截圖”的視頻,截圖完成後將視頻標記爲“等待轉碼”;VideoTranscoderThread會不停地檢查系統中“等待轉碼”的視頻,轉碼完成後將視頻標記爲“完成”。
FFmpeg和Java的整合
FFmpeg和Java整合的過程中有以下幾個需要注意的地方:
(1)路徑的處理
FFmpeg處理視頻的時候需要用到絕對路徑,所以涉及到絕對路徑獲取的問題。使用ServletContext的getRealPath(“/”)方法可以獲得當前Web應用根目錄的絕對路徑。例如在自己的電腦上下述代碼可以獲得Web應用的據對路徑:
ServletContext servletContext;
System.out.println(servletContext.getRealPath("/"));
執行完後輸出爲:D:\MyEclipseWorkspace\.metadata\.me_tcat\webapps\simplest_video_website\
相對路徑中的URL中路徑的分隔採用“/”(正斜槓,Unix系統使用),而上述代碼中路徑的分隔採用“\”(反斜槓,Windows系統使用)。如果把相對文件路徑和絕對目錄路徑拼接起來傳遞給FFmpeg的話,FFmpeg是可以識別的,但是這樣一來路徑中一會“/”一會“\”會給人一種很彆扭的感覺,因此可以使用String的replace()方法將“\”統一替換爲“/”,這樣就整齊多了。例如下面代碼:- ServletContext servletContext;
- System.out.println(servletContext.getRealPath(”/”).replace(‘\\’, ’/’));
ServletContext servletContext;
System.out.println(servletContext.getRealPath("/").replace('\\', '/'));
執行完後輸出爲:D:/MyEclipseWorkspace/.metadata/.me_tcat/webapps/simplest_video_website/
(2)FFmpeg命令行的調用
FFmpeg命令行的的調用可以分成兩個步驟:
(a)生成符合設置的命令
這一步驟實際上就是一個簡單的字符串拼接的過程。根據配置的參數拼接成相應的轉碼命令。需要注意的是,在輸入和輸出的文件路徑兩邊要加上雙引號。否則當文件路徑中包含空格的時候,會導致路徑解析錯誤。
(b)調用命令行
調用命令行使用Runtime.getRuntime().exec(cmdstr)方法就可以了。其中cmdstr常見的格式有兩種:cmd /c {FFmpeg Command}
cmd /c start {FFmpeg Command}
第一種格式是在本窗口中直接執行命令,第二種格式是新打開一個窗口執行命令。第一種方法我在JavaEE環境中測試有問題,而且不彈出窗口不便於調試,所以使用第二種執行方法。
(c)等待調用的命令行執行完畢
Runtime.getRuntime().exec()在調用命令後就直接返回當前線程了。這不符合實際的需求。實際中需要系統完成FFmpeg轉碼工作後,才能做下一步操作。可以用Process的waitFor()方法阻塞當先線程直至調用程序運行結束。
截取縮略圖命令
FFmpeg截取縮略圖命令如下:ffmpeg -y -i "ourtime.flv" -ss 5 -s 220x110 -f image2 -vframes 1 "thumbnail.jpg"
參數含義如下:-y:輸出文件重名的時候,自動覆蓋。
-i:輸入文件路徑(可以是相對路徑或者絕對路徑)。
-ss:截取縮略圖的時間點,這裏是5s處。
-s:輸出縮略圖的分辨率,這裏是220x110。
-f:輸出文件格式,這裏的image2代表文件格式爲圖片。
-vframes:輸出視頻幀的個數,這裏是1。
最後一個參數爲輸出的縮略圖文件路徑。
轉碼命令
FFmpeg轉碼命令如下:- ffmpeg -y -i “ourtime.flv” -vcodec libx264 -b:v 500000 -r 25 -acodec libmp3lame -b:a 64000 -ar 22050 -vf scale=w=640:h=360:force_original_aspect_ratio=decrease,pad=w=640:h=360:x=(ow-iw)/2:y=(oh-ih)/2[aa];movie=svw.png[bb];[aa][bb]overlay=x=5:y=5 “ourtime_convert.flv”
ffmpeg -y -i "ourtime.flv" -vcodec libx264 -b:v 500000 -r 25 -acodec libmp3lame -b:a 64000 -ar 22050 -vf scale=w=640:h=360:force_original_aspect_ratio=decrease,pad=w=640:h=360:x=(ow-iw)/2:y=(oh-ih)/2[aa];movie=svw.png[bb];[aa][bb]overlay=x=5:y=5 "ourtime_convert.flv"
參數含義如下:-y:輸出文件重名的時候,自動覆蓋。
-i:輸入文件路徑(可以是相對路徑或者絕對路徑)。
-vcodec:視頻編碼器,這裏是libx264。
-b:v:視頻碼率,這裏是500000bps。
-r:視頻幀率,這裏是25fps。
-acodec:音頻編碼器,這裏是libmp3lame。
-b:a:音頻碼率,這裏是64000bps。
-ar:音頻採樣率,這裏是22050Hz。
-vf:濾鏡,用於圖像拉伸以及水印疊加。
最後一個參數爲輸出的視頻文件路徑。
Filter(濾鏡)配置
幾句話介紹一下Filter的配置:
- Filter配置參數使用在FFmpeg的-vf參數之後
- Filter之間可以使用“,”連接,連接之後前一個Filter的輸出作爲後一個Filter的輸入。幾個Filter構成一個FilterChain(濾鏡鏈),每個FilterChain以“;”結尾。幾個Filter Chain可以構成一個FilterGraph(濾鏡圖)。可以在Filter的Pad上添加“標籤”用於連接其它Filter,Filter標籤的形式爲“[xx]”(其中“xx”可以隨意寫一些字符,只要可以起到標記作用就可以了)。
- Filter的可以使用多個參數,參數之間使用“:”分割。
下面分別記錄幾個濾鏡的使用方法。
【scale濾鏡】
scale濾鏡使用libswscale對圖像進行拉伸。它的參數含義如下:w:輸出圖像的寬。
h:輸出圖像的高。
force_original_aspect_ratio:保持視頻寬高比的方法,可以使用如下值:(1)disable——不保持寬高比;(2)decrease——需要的時候降低寬或者高;(3)increase——需要的時候提高寬或者高。有關force_original_aspect_ratio可以舉個例子:輸入視頻分辨率是1920x800,設置濾鏡爲:
scale=w=640:h=360:force_original_aspect_ratio=decrease
輸出的視頻分辨率爲640x266。【pad濾鏡】
pad濾鏡用於給拉伸後的圖像加“黑邊”。經過scale濾鏡處理之後,視頻的寬一定小於等於640,而視頻的高一定小於等於360,此時需要使用pad濾鏡填充視頻的兩邊(上下或者是左右),保證輸出的視頻的分辨率爲640x360。
Pad濾鏡有四個基本的參數:
w:填充後視頻的寬度一個基本的如法如下所示:
h:填充後視頻的高度
x:輸入視頻的左上角在填充後視頻中的x座標
y:輸入視頻的左上角在填充後視頻中的y座標
pad=w=640:h=480:x=0:y=40
上述濾鏡將視頻填充爲640x480,同時將輸入視頻的左上角放在(0,40)的位置上。在給視頻加黑邊的過程中,需要把輸入的視頻放在填充後視頻的中央,而pad濾鏡中輸入視頻的位置是以左上角來確定的,因此確定輸入視頻的左上角的座標是一個比較麻煩的事。對此,pad濾鏡提供了以下幾個變量。
iw:輸入視頻的寬通過上述幾個變量,可以得知如果想把輸入視頻放在填充後視頻的中央,輸入視頻的x座標應該爲(ow-iw)/2,y座標應該爲(oh-ih)/2。
ih:輸入視頻的高
ow:輸出視頻的寬
oh:輸出視頻的高
【movie濾鏡】
Movie濾鏡屬於Source濾鏡,它用於讀取水印圖片文件(一般情況下是一個PNG文件)。有關movie濾鏡有一個地方需要特別注意:它在Windows下似乎不支持輸入路徑爲絕對路徑。因爲Windows下文件的絕對路徑通常爲“D:\test\……\watermark.png”,即其中開頭的盤符後面跟着一個“:”。“:”在Filter中是一個特殊字符,會被解析成參數的分隔符,從而導致輸入的絕對路徑被解析爲2個參數,最後造成錯誤。這個問題當時我調試了半天仍然沒有得到解決。因此,Windows下使用movie濾鏡的時候,需要保證水印文件就在當前的工作目錄中,才能正常運行。
【overlay濾鏡】
Overlay濾鏡用於疊加兩路輸入。具體到本文的系統中就是疊加水印文件到視頻文件上。它常用的有兩個參數:
x:水印左上角的x座標
y:水印左上角的y座標
經過上述幾個濾鏡的處理,最終就可以得到一個分辨率爲640x360,保持寬高比(加黑邊),疊加過水印的視頻。
項目主頁
Simplest Video Website
項目主頁
開源中國:http://git.oschina.net/leixiaohua1020/simplest_video_website
Github:https://github.com/leixiaohua1020/simplest_video_website
在線演示
http://www.velab.com.cn:8080/svw/
PS:由於本系統沒有做用戶驗證功能,所以訪客可以隨意添加刪除視頻。最近發現視頻經常被修改或者刪除掉,這樣就失去了演示的意義。還請訪客們儘量少修改演示系統上的視頻……
安裝步驟
1.安裝JavaEE環境:
(1)下載安裝JDK
(2)下載安裝Tomcat
(3)下載安裝MySQL
2.FFmpeg
(1)下載並且解壓縮FFmpeg可執行程序
(2)把FFmpeg的bin目錄(其中包含ffmpeg.exe)添加至系統的”path”環境變量(重要,這樣纔可以在系統任意目錄中使用ffmpeg命令)
3.複製程序
(1)修改Webroot\WEB-INF\classes\hibernate.cfg.xml中數據庫的用戶名和密碼
(2)拷貝WebRoot目錄至Tomcat的webapps目錄,重新取個名字(例如”svw”)
(3)在MySQL中創建數據庫”svw”,在其中執行svw.sql,創建數據庫中的表,並且添加測試數據
4.啓動Tomcat
5.使用瀏覽器訪問http://localhost:8080/svw