最簡單的視頻網站(JavaEE+FFmpeg)

本文記錄一個最簡單的視頻網站系統。此前做過一些基於JavaEE中的SSH (Strut2 + Spring + Hibernate)的網站系統,但是一直沒有做過一個視頻網站系統,所以就打算做一個“精簡”的視頻網站系統,以方便以後測試以及學習使用。本視頻網站支持直播(通過RTMP實現)和點播(通過HTTP實現)。爲了保持精簡,這個視頻網站系統僅製作了網絡視頻的管理功能(增刪改查),以及相關的參數配置功能。由於自己在JavaEE方面沒有深入學習過,所以這個系統有部分功能還沒搞完,以後有時間再慢慢完善。


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文件
  1. <script type=“text/javascript” src=“videoplayer/flowplayer-3.2.8.min.js”></script>  
<script type="text/javascript" src="videoplayer/flowplayer-3.2.8.min.js"></script>
(2)添加一個<a>標記
  1. <a href=“http://www.mywebsite.com/myVideo.flv”  
  2.    style=“display:block;width:425px;height:300px;”  
  3.    id=“player”>  
  4. </a>  
<a href="http://www.mywebsite.com/myVideo.flv"
   style="display:block;width:425px;height:300px;"
   id="player">
</a>
(3)添加一段Javascript代碼,其中要包含FlowPlayer的Swf文件
  1. <script language=“JavaScript”>  
  2.   flowplayer(“player”, “path/to/the/flowplayer-3.2.18.swf”);  
  3. </script>  
<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&amp;width=16&amp;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">&lt;</span><span class="tag-name">a</span><span>&nbsp;</span><span class="attribute">id</span><span>=</span><span class="attribute-value">"player"</span><span>&nbsp;</span><span class="attribute">href</span><span>=</span><span class="attribute-value">" {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>  
  • <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&amp;width=16&amp;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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="tag">&lt;</span><span class="tag-name">a</span><span>&nbsp;</span><span class="attribute">id</span><span>=</span><span class="attribute-value">"player"</span><span class="tag">&gt;</span><span>&nbsp;&nbsp;</span></span></li><li class=""><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tag">&lt;/</span><span class="tag-name">a</span><span class="tag">&gt;</span><span>&nbsp;&nbsp;</span></span></li><li class="alt"><span><span class="comments">&lt;!--&nbsp;Parse&nbsp;RTMP&nbsp;URL&nbsp;--&gt;</span><span>&nbsp;&nbsp;</span></span></li><li class=""><span><span class="tag">&lt;</span><span class="tag-name">script</span><span class="tag">&gt;</span><span>&nbsp;&nbsp;</span></span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="attribute">str</span><span>=</span><span class="attribute-value">' {video.url}’;  
  •       arr=str.split(‘/’);  
  •       //rtmp://server/app/playpath  
  •       protocol=arr[0];  
  •       server=arr[2];  
  •       app=arr[3];  
  •       playpath=arr[4];  
  • lowplayer(“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>  
  •                    <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包含的接口如下所示。

    1. /** 
    2.  * 最簡單的視頻網站 
    3.  * Simplest Video Website 
    4.  * 
    5.  * 雷霄驊 Lei Xiaohua 
    6.  * 
    7.  * [email protected] 
    8.  * 中國傳媒大學/數字電視技術 
    9.  * Communication University of China / Digital TV Technology 
    10.  * http://blog.csdn.net/leixiaohua1020 
    11.  * 
    12.  * 本程序是一個最簡單的視頻網站視頻。它支持 
    13.  * 1.直播 
    14.  * 2.點播 
    15.  * This software is the simplest video website. 
    16.  * It support: 
    17.  * 1. live broadcast 
    18.  * 2. VOD 
    19.  */  
    20. package service;  
    21.    
    22. import java.util.List;  
    23.    
    24. /** 
    25.  * @author 雷霄驊 
    26.  * 對Object的Service 
    27.  * 包含了一些通用的方法 
    28.  */  
    29. public interface BaseService {  
    30.          /** 
    31.           * 保存一個對象 
    32.           * @param object 一個對象 
    33.           */  
    34.           public void save(Object object);  
    35.           /** 
    36.            * 更新一個對象 
    37.            * @param object 一個對象 
    38.            */  
    39.           public void update(Object object);  
    40.           /** 
    41.            * 刪除一個對象 
    42.            * @param object 一個對象 
    43.            */  
    44.           public void delete(Object object);  
    45.           /** 
    46.            * 根據ID讀取一個指定名稱的對象 
    47.            * @param targetName 對象的名稱 
    48.            * @param id 對象的ID 
    49.            * @return 一個對象 
    50.            */  
    51.           public Object ReadByID(String targetName,int id);  
    52.             
    53.           @SuppressWarnings(“rawtypes”)  
    54.           /** 
    55.            * 獲取指定類型的所有對象 
    56.            * @param targetName 對象類型名稱 
    57.            * @return 對象的列表 
    58.            */  
    59.           public List ReadAll(String targetName);  
    60.             
    61.           @SuppressWarnings(“rawtypes”)  
    62.           /** 
    63.            * 根據“屬性-值”獲取多個指定類型的對象 
    64.            * @param targetName 對象類型名稱 
    65.            * @param propertyName 對象中屬性的名稱 
    66.            * @param propertyValue 對象中屬性的值 
    67.            * @return 對象的列表 
    68.            */  
    69.           public List ReadByProperty(String targetName,String propertyName,Object propertyValue);  
    70.    
    71.           /** 
    72.            * 根據“屬性-值”獲取一個指定類型的對象 
    73.            * @param targetName 對象類型名稱 
    74.            * @param propertyName 對象中屬性的名稱 
    75.            * @param propertyValue 對象中屬性的值 
    76.            * @return 一個對象 
    77.            */  
    78.           public Object ReadSingle(String targetName,String propertyName,Object propertyValue);  
    79.             
    80.           /** 
    81.            * 獲取多個指定類型的對象,可以限定獲取對象數目的多少,並且根據特定的屬性進行排序。 
    82.            * @param targetName 對象類型名稱 
    83.            * @param propertyName 對象中屬性的名稱,用於排序 
    84.            * @param num 結果對象列表的最大數目 
    85.            * @param order 排序方式,可以選擇“asc”或者“desc” 
    86.            * @return 對象的列表 
    87.            */  
    88.           public List ReadLimitedByOrder(String targetName,String propertyName,int num,String order);  
    89.             
    90.           /** 
    91.            * 獲取指定類型的對象的數量。 
    92.            * @param targetName 對象類型名稱 
    93.            * @return 數量 
    94.            */  
    95.           public int ReadCount(String targetName);  
    96.           /** 
    97.            * 根據“屬性-值”爲條件,獲取指定類型的對象的數量。 
    98.            * @param targetName 對象類型名稱 
    99.            * @param propertyName 對象中屬性的名稱 
    100.            * @param propertyValue 對象中屬性的值 
    101.            * @return 數量 
    102.            */  
    103.          public int ReadCountByProperty(final String targetName,String propertyName, Object value);  
    104.          /** 
    105.           * 兩個功能: 
    106.           * 1.根據“屬性-值”獲取多個指定類型的對象 
    107.           * 2.限定獲取對象數目的多少,並且根據特定的屬性進行排序。 
    108.           * @param targetName 對象類型名稱 
    109.           * @param readpropertyName 對象中屬性的名稱,用於獲取對象 
    110.           * @param readvalue 對象中屬性的值 
    111.           * @param orderpropertyName 對象中屬性的名稱,用於排序 
    112.           * @param num 結果對象列表的最大數目 
    113.           * @param order 排序方式,可以選擇“asc”或者“desc” 
    114.           * @return 
    115.           */  
    116.          public List ReadByPropertyAndLimitedByOrder(final String targetName, final String readpropertyName,  
    117.                             final Object readvalue,final String orderpropertyName, final int num, final String order);  
    118.    
    119. }  
    /**
     * 最簡單的視頻網站
     * 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應用的據對路徑:

    1. ServletContext servletContext;  
    2. System.out.println(servletContext.getRealPath(”/”));  
    ServletContext servletContext;
    System.out.println(servletContext.getRealPath("/"));
    執行完後輸出爲:
    1. D:\MyEclipseWorkspace\.metadata\.me_tcat\webapps\simplest_video_website\  
    D:\MyEclipseWorkspace\.metadata\.me_tcat\webapps\simplest_video_website\
    相對路徑中的URL中路徑的分隔採用“/”(正斜槓,Unix系統使用),而上述代碼中路徑的分隔採用“\”(反斜槓,Windows系統使用)。如果把相對文件路徑和絕對目錄路徑拼接起來傳遞給FFmpeg的話,FFmpeg是可以識別的,但是這樣一來路徑中一會“/”一會“\”會給人一種很彆扭的感覺,因此可以使用String的replace()方法將“\”統一替換爲“/”,這樣就整齊多了。例如下面代碼:
    1. ServletContext servletContext;  
    2. System.out.println(servletContext.getRealPath(”/”).replace(‘\\’, ’/’));  
    ServletContext servletContext;
    System.out.println(servletContext.getRealPath("/").replace('\\', '/'));
    執行完後輸出爲:
    1. D:/MyEclipseWorkspace/.metadata/.me_tcat/webapps/simplest_video_website/  
    D:/MyEclipseWorkspace/.metadata/.me_tcat/webapps/simplest_video_website/
     

    (2)FFmpeg命令行的調用

    FFmpeg命令行的的調用可以分成兩個步驟:

    (a)生成符合設置的命令

    這一步驟實際上就是一個簡單的字符串拼接的過程。根據配置的參數拼接成相應的轉碼命令。需要注意的是,在輸入和輸出的文件路徑兩邊要加上雙引號。否則當文件路徑中包含空格的時候,會導致路徑解析錯誤。

    (b)調用命令行

    調用命令行使用Runtime.getRuntime().exec(cmdstr)方法就可以了。其中cmdstr常見的格式有兩種:
    1. cmd /c {FFmpeg Command}  
    2. cmd /c start {FFmpeg Command}  
    cmd /c {FFmpeg Command}
    cmd /c start {FFmpeg Command}

    第一種格式是在本窗口中直接執行命令,第二種格式是新打開一個窗口執行命令。第一種方法我在JavaEE環境中測試有問題,而且不彈出窗口不便於調試,所以使用第二種執行方法。

    (c)等待調用的命令行執行完畢

    Runtime.getRuntime().exec()在調用命令後就直接返回當前線程了。這不符合實際的需求。實際中需要系統完成FFmpeg轉碼工作後,才能做下一步操作。可以用Process的waitFor()方法阻塞當先線程直至調用程序運行結束。


    截取縮略圖命令

    FFmpeg截取縮略圖命令如下:
    1. ffmpeg -y -i “ourtime.flv” -ss 5 -s 220x110 -f image2 -vframes 1 “thumbnail.jpg”  
    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轉碼命令如下:
    1. 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,設置濾鏡爲:
    1. scale=w=640:h=360:force_original_aspect_ratio=decrease  
    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座標
    一個基本的如法如下所示:
    1. pad=w=640:h=480:x=0:y=40  
    pad=w=640:h=480:x=0:y=40
    上述濾鏡將視頻填充爲640x480,同時將輸入視頻的左上角放在(0,40)的位置上。
    在給視頻加黑邊的過程中,需要把輸入的視頻放在填充後視頻的中央,而pad濾鏡中輸入視頻的位置是以左上角來確定的,因此確定輸入視頻的左上角的座標是一個比較麻煩的事。對此,pad濾鏡提供了以下幾個變量。
    iw:輸入視頻的寬
    ih:輸入視頻的高
    ow:輸出視頻的寬
    oh:輸出視頻的高
    通過上述幾個變量,可以得知如果想把輸入視頻放在填充後視頻的中央,輸入視頻的x座標應該爲(ow-iw)/2,y座標應該爲(oh-ih)/2。
     
    【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



     
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章