頻盛行的時代,流媒體技術越來越重要,在本文中,我們將討論兩種流行的流媒體協議:RTMP 和 RTSP。
簡單地說,流協議就是在兩個通信系統之間傳輸多媒體文件的一套規則,它定義了視頻文件將如何分解為小數據包以及它們在互聯網上傳輸的順序,RTMP 與 RTSP 是比較常見的流媒體協議。
還記得幾年前,當您嘗試播放視頻時,您的計算機會告訴您更新您的 Flash 插件嗎?這背后的技術其實就是RTMP。
RTMP由 Macromedia(后來被 Adobe 收購)開發,用于流式傳輸到 Flash 播放器,隨著 Flash 開始被淘汰并且基于 HTTP 的協議成為流式傳輸到播放設備的新標準,RTMP在流媒體協議中的地位越來越低。但是絲毫不影響RTMP的使用,因為在端到端直播和點播中,它仍然是王者!
【相關學習資料推薦,點擊下方鏈接免費報名,先碼住不迷路~】
【免費】FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級開發-學習視頻教程-騰訊課堂
C++音視頻配套學習資料:點擊莬費領取→音視頻開發(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)
RTMP使用獨占的 1935 端口,無需緩沖,可以實現低延遲。
所有 RTMP 服務器都可以錄制直播媒體流,同時還允許觀眾跳過部分廣播并在直播開始后加入直播流。
RTMP 支持整合文本、視頻和音頻,支持 MP3 和 AAC 音頻流,也支持MP4、FLV 和 F4V 視頻。
標準HTML5 播放器不支持 RTMP 流。
RTMP 流經常會出現低帶寬問題,造成視頻中斷。
無法通過 HTTP 流式傳輸 RTMP,必須需要實現一個特殊的服務器,并使用第三方內容交付網絡或使用流媒體視頻平臺。
RTSP旨在建立和維護原始數據源(客戶端)和流服務器之間的連接,允許控制流媒體服務器內的娛樂和通信系統,允許暫停和播放功能,RTSP是 RealNetworks于 1996 年 與 Netscape 和哥倫比亞大學聯合開發的協議。
這種可靠性和控制性的結合使RTSP成為閉路電視 (CCTV) 和類似 監控系統的熱門選擇。因此,它是許多 IP 攝像機的首選協議。
可以通過結合不同的協議來開發自己的視頻流解決方案。
RTSP 流使觀看者能夠在下載完成之前訪問的視頻內容,而不必下載完整的視頻以流式傳輸內容。
沒有簡單的解決方案可以在 Web 瀏覽器中播放 RTSP流,因為 RTSP 旨在通過私有網絡流式傳輸視頻,必須借用額外軟件。
由于視頻播放器和流媒體服務并未廣泛支持 RTSP 流媒體,因為使用率比較低。
C++音視頻配套學習資料:點擊莬費領取→音視頻開發(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)
幾乎所有 IP 攝像機都支持 RTSP,這是因為 IP 攝像機早在 RTMP 協議創建之前就已經存在,與 RTSP 和 IP 攝像機結合使用時,IP 攝像機本身充當 RTSP 服務器,這意味著要將攝像機連接到 IP 攝像機服務器并廣播視頻。
RTSP 通常內置在無人機或物聯網軟件中,從而可以訪問視頻源,它的好處之一是低延遲,確保視頻中沒有延遲,這對于無人機來說至關重要。
比如各種短視頻軟件、視頻軟件等都內置了RTMP,RTMP 是為滿足現代流媒體需求而設計的。
RTMP和RTSP是最常用的流媒體協議,本文主要介紹了RTMP和RTSP的概念以及優缺點,對于RTMP和RTSP的選擇,還是得看場景。希望本文對您認識RTMP和RTSP有所幫助,最后感謝您的閱讀,如果覺得文章對您有幫助,別忘了點贊、收藏?哦!有任何問題,歡迎在下方評論區與我討論!!!
視頻點播需求如下:
1、學生可以在windows瀏覽器上在線觀看視頻。
2、播放器具有快進、快退、暫停等基本功能。
詳細參考:https://baike.baidu.com/item/%E6%B5%81%E5%AA%92%E4%BD%93/98740?fr=aladdin
概括理解:流媒體就是將視頻文件分成許多小塊兒,將這些小塊兒作為數據包通過網絡發送出去,實現一邊傳輸視頻 數據 包一邊觀看視頻。
在網絡上傳輸音、視頻信息有兩個方式:下載和流式傳輸。
下載:就是把音、視頻文件完全下載到本機后開始播放,它的特點是必須等到視頻文件下載完成方可播放,播放等待時間較長,無法去播放還未下載的部分視頻。
流式傳輸:就是客戶端通過鏈接視頻服務器實時傳輸音、視頻信息,實現“邊下載邊播放”。
流式傳輸包括如下兩種方式:
1) 順序流式傳輸
即順序下載音、視頻文件,可以實現邊下載邊播放,不過,用戶只能觀看已下載的視頻內容,無法快進到未下載的視頻部分,順序流式傳輸可以使用Http服務器來實現,比如Nginx、Apache等。
2)實時流式傳輸
實時流式傳輸可以解決順序流式傳輸無法快進的問題,它與Http流式傳輸不同,它必須使用流媒體服務器并且使用流媒體協議來傳輸視頻,它比Http流式傳輸復雜。常見的實時流式傳輸協議有RTSP、RTMP、RSVP等。
通過流媒體系統的概要結構學習流媒體系統的基本業務流程。
1、將原始的視頻文件通過編碼器轉換為適合網絡傳輸的流格式,編碼后的視頻直接輸送給媒體服務器。
原始的視頻文件通常是事先錄制好的視頻,比如通過攝像機、攝像頭等錄像、錄音設備采集到的音視頻文件,體積較大,要想在網絡上傳輸需要經過壓縮處理,即通過編碼器進行編碼 。
2、媒體服務獲取到編碼好的視頻文件,對外提供流媒體數據傳輸接口,接口協議包括 :HTTP、RTSP、RTMP等
3、播放器通過流媒體協議與媒體服務器通信,獲取視頻數據,播放視頻。
本項目包括點播和直播兩種方式,我們先調研點播的方案,如下:
1、 播放器通過 http協議從http服務器上下載視頻文件進行播放
問題:必須等到視頻下載完才可以播放,不支持快進到某個時間點進行播放
2、 播放器通過rtmp協議連接媒體服務器以實時流方式播放視頻
使用rtmp協議需要架設媒體服務器,造價高,對于直播多采用此方案。
3、 播放器使用HLS協議連接http服務器(Nginx、Apache等)實現近實時流方式播放視頻
HLS協議規定:基于Http協議,視頻封裝格式為ts,視頻的編碼格式為H264,音頻編碼格式為MP3、AAC或者AC-3
HLS的工作方式是:將視頻拆分成若干ts格式的小文件,通過m3u8格式的索引文件對這些ts小文件建立索引。一般10秒一個ts文件,播放器連接m3u8文件播放,當快進時通過m3u8即可找到對應的索引文件,并去下載對應的ts文件,從而實現快進、快退以近實時 的方式播放視頻。
IOS、Android設備、及各大瀏覽器都支持HLS協議。
詳細參考:https://baike.baidu.com/item/HLS/8328931?fr=aladdin
采用HLS方案即可實現邊下載邊播放,可以不用使用rtmp等流媒體協議,不用構建專用的媒體服務器,節省成本。
本項目點播方案確定為方案3。
我們將視頻錄制完成后,使用視頻編碼軟件對視頻進行編碼,本項目 使用FFmpeg對視頻進行編碼 。
下載 :ffmpeg-20180227-fa0c9d6-win64-static.zip,并解壓,本教程將ffmpeg解壓到了C:\Java_Soft\xczx\ffmpeg-20180227-fa0c9d6-win64-static下。
FFmpeg被許多開源項目采用,QQ影音、暴風影音、VLC等。
下載:FFmpeg https://www.ffmpeg.org/download.html#build-windows
將C:\Java_Soft\xczx\ffmpeg-20180227-fa0c9d6-win64-static\bin目錄配置在path環境變量中。
簡單的測試:
將一個.avi文件轉成mp4、mp3、gif等。
比如我們將lucene.avi文件轉成mp4,運行如下命令:
ffmpeg -i lucene.avi lucene.mp4
轉成mp3:ffmpeg -i lucene.avi lucene.mp3
轉成gif:ffmpeg -i lucene.avi lucene.gif
官方文檔(英文):http://ffmpeg.org/ffmpeg.html
使用ffmpeg生成 m3u8的步驟如下:
第一步:先將avi視頻轉成mp4
ffmpeg.exe -i lucene.avi -c:v libx264 -s 1280x720 -pix_fmt yuv420p -b:a 63k -b:v 753k -r 18 .\lucene.mp4
下面把各參數意思大概講講,大概了解意思即可,不再此展開流媒體專業知識的講解。
-c:v 視頻編碼為x264 ,x264編碼是H264的一種開源編碼格式。
-s 設置分辨率
-pix_fmt yuv420p:設置像素采樣方式,主流的采樣方式有三種,YUV4:4:4,YUV4:2:2,YUV4:2:0,它的作用是根據采樣方式來從碼流中還原每個像素點的YUV(亮度信息與色彩信息)值。
-b 設置碼率,-b:a和-b:v分別表示音頻的碼率和視頻的碼率,-b表示音頻加視頻的總碼率。碼率對一個視頻質量有很大的作用。
-r:幀率,表示每秒更新圖像畫面的次數,通常大于24肉眼就沒有連貫與停頓的感覺了。
第二步:將mp4生成m3u8
ffmpeg -i lucene.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8
-hls_time 設置每片的長度,單位為秒
-hls_list_size n: 保存的分片的數量,設置為0表示保存所有分片
-hls_segment_filename :段文件的名稱,%05d表示5位數字
生成的效果是:將lucene.mp4視頻文件每10秒生成一個ts文件,最后生成一個m3u8文件,m3u8文件是ts的索引文件。
使用VLC打開m3u8文件,測試播放效果,VLC 是一款自由、開源的跨平臺多媒體播放器及框架,可播放大多數多媒體文件,以及 DVD、音頻 CD、VCD 及各類流媒體協議。(http://www.videolan.org/)
導入shcool.sql
基于springboot創建工程
通常視頻文件都比較大,所以對于媒資系統上傳文件的需求要滿足大文件的上傳要求。http協議本身對上傳文件大小沒有限制,但是客戶的網絡環境質量、電腦硬件環境等參差不齊,如果一個大文件快上傳完了網斷了,電斷了沒有上傳完成,需要客戶重新上傳,這是致命的,所以對于大文件上傳的要求最基本的是斷點續傳。
什么是斷點續傳:
引用百度百科:斷點續傳指的是在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包)人為的劃分為幾個部分,每一個部分采用一個線程進行上傳或下載,如果碰到網絡故障,可以從已經上傳或下載的部分開始繼續上傳下載未完成的部分,而沒有必要從頭開始上傳下載,斷點續傳可以提高節省操作時間,提高用戶體驗性。
上傳流程如下:
1、上傳前先把文件分成塊
2、一塊一塊的上傳,上傳中斷后重新上傳,已上傳的分塊則不用再上傳
3、各分塊上傳完成最后合并文件
文件下載則同理。
為了更好的理解文件分塊上傳的原理,下邊用java代碼測試文件的分塊與合并。
文件分塊的流程如下:
1、獲取源文件長度
2、根據設定的分塊文件的大小計算出塊數
3、從源文件讀數據依次向每一個塊文件寫數據
//測試文件分塊方法
@Test
public void testChunk() throws IOException {
File sourceFile = new File("F:/develop/ffmpeg/lucene.mp4");
// File sourceFile = new File("d:/logo.png");
String chunkPath = "F:/develop/ffmpeg/chunk/";
File chunkFolder = new File(chunkPath);
if(!chunkFolder.exists()){
chunkFolder.mkdirs();
}
//分塊大小
long chunkSize = 1024*1024*1;
//分塊數量
long chunkNum = (long) Math.ceil(sourceFile.length() * 1.0 / chunkSize );
if(chunkNum<=0){
chunkNum = 1;
}
//緩沖區大小
byte[] b = new byte[1024];
//使用RandomAccessFile訪問文件
RandomAccessFile raf_read = new RandomAccessFile(sourceFile, "r");
//分塊
for(int i=0;i<chunkNum;i++){
//創建分塊文件
File file = new File(chunkPath+i);
boolean newFile = file.createNewFile();
if(newFile){
//向分塊文件中寫數據
RandomAccessFile raf_write = new RandomAccessFile(file, "rw");
int len = -1;
while((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
if(file.length()>chunkSize){
break;
}
}
raf_write.close();
}
}
raf_read.close();
?
}
文件合并流程:
1、找到要合并的文件并按文件合并的先后進行排序
2、創建合并文件
3、依次從合并的文件中讀取數據向合并文件寫入數據
//測試文件合并方法
@Test
public void testMerge() throws IOException {
//塊文件目錄
File chunkFolder = new File("F:/develop/ffmpeg/chunk/");
//合并文件
File mergeFile = new File("F:/develop/ffmpeg/lucene1.mp4");
if(mergeFile.exists()){
mergeFile.delete();
}
//創建新的合并文件
mergeFile.createNewFile();
//用于寫文件
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
//指針指向文件頂端
raf_write.seek(0);
//緩沖區
byte[] b = new byte[1024];
//分塊列表
File[] fileArray = chunkFolder.listFiles();
// 轉成集合,便于排序
List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));
// 從小到大排序
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
return -1;
}
return 1;
}
});
//合并文件
for(File chunkFile:fileList){
RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"rw");
int len = -1;
while((len=raf_read.read(b))!=-1){
raf_write.write(b,0,len);
?
}
raf_read.close();
}
raf_write.close();
?
}
如何在web頁面實現斷點續傳?
常見的方案有:
1、通過Flash上傳,比如SWFupload、Uploadify。
2、安裝瀏覽器插件,變相的pc客戶端,用的比較少。
3、Html5
隨著html5的流行,本項目采用Html5完成文件分塊上傳。
本項目使用WebUploader完成大文件上傳功能的開發,WebUploader官網地址:http://fexteam.gz01.bdysite.com/webuploader/
使用WebUploader上傳流程如下:
在webuploader中提供很多鉤子方法,下邊列出一些重要的:
本項目使用如下鉤子方法:
1)before-send-file
在開始對文件分塊兒之前調用,可以做一些上傳文件前的準備工作,比如檢查文件目錄是否創建完成等。
2)before-send
在上傳文件分塊之前調用此方法,可以請求服務端檢查分塊是否存在,如果已存在則此分塊兒不再上傳。
3)after-send-file
在所有分塊上傳完成后觸發,可以請求服務端合并分塊文件。
注冊鉤子方法源代碼:
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile"
}
使用webUploader前需要創建webUploader對象。
指定上傳分塊的地址:/api/media/upload/uploadchunk
// 創建uploader對象,配置參數
this.uploader = WebUploader.create(
{
swf:"/static/plugins/webuploader/dist/Uploader.swf",//上傳文件的flash文件,瀏覽器不支持h5時啟動flash
server:"/api/media/upload/uploadchunk",//上傳分塊的服務端地址,注意跨域問題
fileVal:"file",//文件上傳域的name
pick:"#picker",//指定選擇文件的按鈕容器
auto:false,//手動觸發上傳
disableGlobalDnd:true,//禁掉整個頁面的拖拽功能
chunked:true,// 是否分塊上傳
chunkSize:1*1024*1024, // 分塊大小(默認5M)
threads:3, // 開啟多個線程(默認3個)
prepareNextFile:true// 允許在文件傳輸時提前把下一個文件準備好
}
)
文件開始上傳前前端請求服務端準備上傳工作。
type:"POST",
url:"/api/media/upload/register",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
上傳分塊前前端請求服務端校驗分塊是否存在。
type:"POST",
url:"/api/media/upload/checkchunk",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
// 當前分塊下標
chunk:block.chunk,
// 當前分塊大小
chunkSize:block.end-block.start
}
在所有分塊上傳完成后觸發,可以請求服務端合并分塊文件。
type:"POST",
url:"/api/media/upload/mergechunks",
data:{
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
學員使用:
server {
listen 82;
server_name localhost;
#視頻中心
location / {
proxy_pass http://127.0.0.1:12000;
proxy_set_header Host $http_host;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods GET;
}
?
#媒資管理后臺跨域
location ^~ /api/media/ {
proxy_pass http://127.0.0.1:9000/media/;
?
proxy_set_header Host $http_host;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods "GET,POST,OPTIONS";
}
}
可以訪問 http://127.0.0.1:12000/#/media/upload 或者 http://localhost:82/#/media/upload/ 查看頁面效果。
服務端需要實現如下功能:
1、上傳前檢查上傳環境
檢查文件是否上傳,已上傳則直接返回。
檢查文件上傳路徑是否存在,不存在則創建。
2、分塊檢查
檢查分塊文件是否上傳,已上傳則返回true。
未上傳則檢查上傳路徑是否存在,不存在則創建。
3、分塊上傳
將分塊文件上傳到指定的路徑。
4、合并分塊
將所有分塊文件合并為一個文件。
在數據庫記錄文件信息。
application.yml配置上傳文件的路徑:
xc-service-manage-media:
upload-location: C:/school/video/ # 媒資保存路徑
ffmpeg-path: C:/Java_Soft/xczx/ffmpeg-20180227-fa0c9d6-win64-static/bin/ffmpeg.exe # ffmpeg路徑
使用mybatis-plus
@Mapper
@Component
public interface FileMsgMapper extends BaseMapper<FileMsg> {
}
@RestController
@RequestMapping("/media/upload")
public class MediaUploadController {
?
?
@Autowired
MediaUploadService mediaUploadService;
?
/**
* 文件上傳前的注冊
*/
?
@PostMapping("/register")
public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
?
return mediaUploadService.register(fileMd5, fileName, fileSize, mimetype, fileExt);
?
}
?
/**
* 檢測分塊
*
* @param fileMd5
* @param chunk
* @param chunkSize
* @return
*/
?
@PostMapping("/checkchunk")
public ResponseResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
return mediaUploadService.checkchunk(fileMd5, chunk, chunkSize);
}
?
/**
* 上傳分塊
*
* @param file
* @param fileMd5
* @param chunk
* @return
*/
@PostMapping("/uploadchunk")
public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
return mediaUploadService.uploadchunk(file, fileMd5, chunk);
}
?
/**
* 合并分塊
*
* @param fileMd5
* @param fileName
* @param fileSize
* @param mimetype
* @param fileExt
* @return
*/
@PostMapping("/mergechunks")
public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
return mediaUploadService.mergechunks(fileMd5, fileName, fileSize, mimetype, fileExt);
}
}
?
現在視頻已經通過斷點續傳的方式,上傳到了我們的媒資服務器, 接下來就要使用FFmpeg將視頻轉換成 流媒體。
如何通過程序進行視頻處理?
ffmpeg是一個可行的視頻處理程序,可以通過Java調用ffmpeg.exe完成視頻處理。
在java中可以使用Runtime類和Process Builder類兩種方式來執行外部程序,工作中至少掌握一種。
本項目使用Process Builder的方式來調用ffmpeg完成視頻處理。
關于Process Builder的測試如下 :
@Test
public void testProcessBuilder(){
ProcessBuilder processBuilder = new ProcessBuilder();
// processBuilder.command("ping","127.0.0.1");
processBuilder.command("ipconfig");
//將標準輸入流和錯誤輸入流合并,通過標準輸入流讀取信息
processBuilder.redirectErrorStream(true);
try {
//啟動進程
Process start = processBuilder.start();
//獲取輸入流
InputStream inputStream = start.getInputStream();
//轉成字符輸入流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
int len = -1;
char[] c = new char[1024];
StringBuffer outputString = new StringBuffer();
//讀取進程輸入流中的內容
while ((len= inputStreamReader.read(c))!=-1) {
String s = new String(c,0,len);
outputString.append(s);
System.out.print(s);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testFFmpeg(){
ProcessBuilder processBuilder = new ProcessBuilder();
//定義命令內容
List<String> command = new ArrayList<>();
command.add("D:\\Program Files\\ffmpeg-20180227-fa0c9d6-win64-static\\bin\\ffmpeg.exe");
command.add("-i");
command.add("E:\\ffmpeg_test\\1.avi");
command.add("-y");//覆蓋輸出文件
command.add("-c:v");
command.add("libx264");
command.add("-s");
command.add("1280x720");
command.add("-pix_fmt");
command.add("yuv420p");
command.add("-b:a");
command.add("63k");
command.add("-b:v");
command.add("753k");
command.add("-r");
command.add("18");
command.add("E:\\ffmpeg_test\\1.mp4");
processBuilder.command(command);
//將標準輸入流和錯誤輸入流合并,通過標準輸入流讀取信息
processBuilder.redirectErrorStream(true);
try {
//啟動進程
Process start = processBuilder.start();
//獲取輸入流
InputStream inputStream = start.getInputStream();
//轉成字符輸入流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
int len = -1;
char[] c = new char[1024];
StringBuffer outputString = new StringBuffer();
//讀取進程輸入流中的內容
while ((len= inputStreamReader.read(c))!=-1) {
String s = new String(c,0,len);
outputString.append(s);
System.out.print(s);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
我們已經準備好了工具類:
Mp4VideoUtil.java完成avi轉mp4
HlsVideoUtil.java完成mp4轉hls
可以直接使用。
當視頻合并成功后
具體代碼: 在合并完成方法中調用
@Value("${xc-service-manage-media.ffmpeg-path}")
String ffmpeg_path;
?
?
private void ChangeHLS(File mergeFile, String fileMd5) {
?
// mp4 文件保存目錄
String fileFolderPath = getFileFolderPath(fileMd5);
//生成的mp4的文件名稱
String mp4_name = fileMd5 + ".mp4";
//生成的mp4所在的路徑
String mp4folder_path = fileFolderPath;
?
?
//創建工具類對象
Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, mergeFile.getAbsolutePath(), mp4_name, mp4folder_path);
?
//進行處理
String result = mp4VideoUtil.generateMp4();
?
?
//4、將mp4生成m3u8和ts文件
//String ffmpeg_path, String video_path, String m3u8_name,String m3u8folder_path
//mp4視頻文件路徑
String mp4_video_path = mp4folder_path + mp4_name;
?
//m3u8_name文件名稱
String m3u8_name = fileMd5 + ".m3u8";
//m3u8文件所在目錄
String m3u8folder_path = fileFolderPath + "hls/";
?
HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path, mp4_video_path, m3u8_name, m3u8folder_path);
?
//生成m3u8和ts文件
String tsResult = hlsVideoUtil.generateM3u8();
?
//保存fileUrl(此url就是視頻播放的相對路徑)
String filePath = fileFolderPath + "hls/" + m3u8_name;
?
?
// 將fileUrl 保存到數據庫
FileMsg fileMsg = new FileMsg();
fileMsg.setFileId(fileMd5);
fileMsg.setFileName(mergeFile.getName());
fileMsg.setFilePath(filePath);
fileMsg.setFileUrl(filePath.split("C:/school/video")[1]);
?
fileMsgMapper.insert(fileMsg);
?
?
//優化: 文件處理成功后, 可以刪除mp4文件
?
?
}
說明:
mp4轉成m3u8如何判斷轉換成功?
第一、根據視頻時長來判斷,同mp4轉換成功的判斷方法。
第二、最后還要判斷m3u8文件內容是否完整。
視頻編碼后要使用播放器對其進行解碼、播放視頻內容。在web應用中常用的播放器有flash播放器、H5播放器或瀏覽器插件播放器,其中以flash和H5播放器最常見。
flash播放器:缺點是需要在客戶機安裝Adobe Flash Player播放器,優點是flash播放器已經很成熟了,并且瀏覽器對flash支持也很好。
H5播放器:基于h5自帶video標簽進行構建,優點是大部分瀏覽器支持H5,不用再安裝第三方的flash播放器,并且隨著前端技術的發展,h5技術會越來越成熟。
本項目采用H5播放器,使用Video.js開源播放器。
Video.js是一款基于HTML5世界的網絡視頻播放器。它支持HTML5和Flash視頻,它支持在臺式機和移動設備上播放視頻。這個項目于2010年中開始,目前已在40萬網站使用。
官方地址:http://videojs.com/
Video.js: https://github.com/videojs/video.js
videojs-contrib-hls: https://github.com/videojs/videojs-contrib-hls#installation
(videojs-contrib-hls是播放hls的一個插件)
使用文檔:http://docs.videojs.com/tutorial-videojs_.html
本教程使用 video.js 6.7.3 版本,videojs-contrib-hls 5.14.1版本
將資料中提供的plugins賦值到xc-ui-pc-video項目目錄下
正常使用video.js播放視頻是通過一個網頁,用戶通過瀏覽器打開網頁去播放視頻,網頁和視頻都從web服務器請求。
根據上邊的流程,我們在媒體服務器上安裝Nginx,并配置如下 :
server {
listen 81;
server_name localhost;
# 流媒體 靜態資源
location ^~ /school/video/ {
alias C:/school/video/;
}
# 視頻靜態資源
location / {
alias C:/CODE/JAVA/school/xc-ui-pc-video/;
index index.html index.htm;
}
}
上面已經使用nginx 代理 C:/CODE/JAVA/school/xc-ui-pc-video/ 目錄。 所以可以通過http請求直接訪問video.html頁面
用戶打開瀏覽器輸入 http://localhost:81/video.html 。即可打開視頻播放頁面,并播放。
要:基于WebGL和視頻圖傳技術,利用傾斜攝影實景三維數據,分析構建視頻監控圖像與仿真現實地理空間高度融合場景的技術方法。完整闡述場景構建、信號接入、投影融合、圖像拼接的技術路線,詳述融合的數學模型和編程實現,并結合多年市場調研經驗,介紹本項研究的市場應用前景。
關鍵詞: WebGL 傾斜攝影 實景三維數據 投影融合 測繪學 視頻圖傳
在地理信息系統(GIS)領域中,基于傾斜攝影測量技術制作的實景三維場景具有多項優勢[1],不僅能完整地還原地形地貌,尤其是城市環境中的建筑物外立面、近地面廣告牌等環境細節,而且具有高精度的可量測性[2],可進行全角度的三維測量。但是,實景三維場景是某一時間節點的測量成果,本質上屬于靜態地圖[3],數據的現勢性問題無法避免。為解決這個問題,實景三維GIS越來越多地接入物聯網傳感器,融合現實世界的動態情況,來滿足各自的業務需求[4]。如接入定位信號,在實景三維場景中顯示人員和車輛實時位置等。其中一項最重要的物聯網傳感器就是視頻監控設備,負責將現實世界的實時圖像信息接入由實景三維數據構建的虛擬現實場景,并得到廣泛應用。然而單一視頻監控圖像的視域范圍有限,多路視頻切換或分屏播放都會導致監控目標失去焦點,影響監控效果[5]。同時,屏幕化的視頻監控圖像孤立于周邊環境,不能通過屏幕畫面直觀地了解視頻監控圖像在現實世界中的確切位置,及其與周邊環境的關系。因此,將視頻監控圖像以幾何投影方式貼合到地面(簡稱視頻貼地),融合到實景三維場景中,多路視頻畫面在空間上拼接,形成較大區域的連續畫面,確立完整的視頻監控環境成為GIS應用研究中一項十分必要的工作。
實現實景三維場景與視頻監控圖像融合的思路主要包括桌面端和瀏覽器端兩個方面,本文將主要論述瀏覽器端的研究。
1、基于WebGL構建的實景三維場景
WebGL(WebGraphicsLibrary)是一種三維繪圖協議,把JavaScript和OpenGLES2.0結合在一起,為HTML5Canvas提供硬件三維加速渲染[6]。這樣就可以在網頁瀏覽器直接渲染實景三維場景,而不需要外置插件。目前,常用的WebGL二次開發接口有CesiumJS和three.js,它們都有完善的地理坐標系接口,可以用于開發標準的GIS應用項目。本文以CesiumJS接口庫為例,構建實景三維場景,因為CesiumJS定義了適宜于網絡傳輸的實景三維數據格式:3DTiles,這種數據是以樹形結構組織起來的瓦片(tiles)集合,并且可以由實景三維建模軟件ContextCapture直接生產[7]。
CesiumJS建立實景三維場景的方法十分簡潔:首先,創建場景對象(Viewer);然后,創建實景三維數據對象(Cesium3DTileset);最后,向場景對象中添加(viewer.scene.primitives.add)對象即可,如圖1所示。
圖1CesiumJS建立實景三維的方法
但需要注意的是實景三維數據的高度,與CesiumJS默認的“地球”之間會存在空隙,因此,還要利用CesiumTerrainProvider對象引入地形數據,最終形成完整的環境效果。
2、視頻監控圖像信號接入
視頻監控圖像接入場景首先要獲取到它的數據流,不同廠商和型號的數據流使用的編碼標準不盡相同,為了具備更好的兼容性,需要使用通用的網絡流媒體協議RTSP(Real-TimeStreamProtocol)進行應用層的數據解析[8]。各廠商設備的RTSP協議取流方法有所區別,但是基本格式相似,都主要包括了IP地址、端口和通道3個參數,以海康設備為例,格式如下:
rtsp://username:password@<address>:<port>/Streaming/Channels/<id>。
但是,目前的HTML5的video標簽仍然無法直接播放RTSP直播數據流,研究中使用了開源視頻播放器VLC提供的流媒體轉碼功能將RTSP轉碼為可以直接播放的OGG格式流數據。VLC的轉碼命令為#transcode,在命令處理工具中執行即可[9]。多路視頻同時轉碼只需要創建VLC命令的JavaScript腳本,利用Node.js執行即可。但是,此時的OGG格式流數據存在跨域訪問問題,需要在Nginx中代理,并添加頭配置。經過代理的OGG格式流數據可以直接被HTML5播放,配置參數如下:add_headerAccess-Control-Allow-Origin*。
CesiumJS在實景三維場景中接入視頻資源的方法是將video標簽作為材質對象(Material)賦予渲染基元(Primitive),然后在場景中創建出來即可最終實現接入。總結下來,接入流程如圖2所示。
3、視頻監控圖像投影融合
實景三維場景完美地還原了現實世界的空間關系,相機拍攝的畫面本質上是鏡頭曝光平面沿著視錐體在地面上的投影[10]。而視頻監控圖像與實景三維場景融合的關鍵問題就是將監控圖像與場景中的同名點配準,實現同名點的重合,也就是將畫面逐個像素的坐標從本地坐標系轉變到視錐體裁剪坐標系[11]中。依據這個思路,在實景三維場景中,同樣還原相機的空間位置和安裝姿態,并且模擬鏡頭的視錐體,如圖3所示,在視平面還原視頻監控圖像,利用視錐體獲得其在實景三維數據表面的投影范圍,即可實現圖像投影融合。
圖2CesiumJS接入視頻流程
圖3鏡頭模擬的視椎體
還原相機所需的主要參數包括表1中幾項,這些參數可以通過測量和查看設備說明書獲得。
表1相機主要參數
視頻貼地的渲染基元采用分類基元類(ClassificationPrimitive),其一致性映射(_uniformMap)參數控制webgl渲染的深度計算和坐標轉換等映射方式,包括立方體模型視圖(u_boxMV)和逆立方體模型視圖(u_inverseBoxMV)兩個變量,這兩個變量用于執行透視除法,確定視頻裁剪后在實景三維數據表面貼地范圍。其計算方法參照WebGL投影變換的基本原理[12],示意圖如圖4所示。
圖4WebGL投影變換基本原理
首先將相機視錐體原點定位到地理坐標,再利用四維矩陣的計算辦法將地理坐標、朝向、俯角、旋轉角4個參數共同計算組成相機類(Camera)的視圖矩陣(viewMatrix)和逆視圖矩陣(inverseViewMatrix)。然后,使用透視錐體類(PerspectiveFrustum)創建視錐體,包括視場角、寬高比、焦距、拍攝距離4個參數。透視錐體類的投影矩陣參數(projectionMatrix)可以獲取該視錐體的透視投影矩陣,經過反轉(Matrix4.inverse)并與逆視圖矩陣相乘(Matrix4.multiply)得到逆視圖投影矩陣,結果再反轉即可得到視圖投影矩陣。此時得到的視圖投影矩陣可以用于完成坐標變換,利用拍攝距離和四維矩陣的創建辦法(Matrix4.fromUniformScale)生成遠平面標準模型矩陣,如下所示:
[拍攝距離,0.0,0.0,0.0]
[0.0,拍攝距離,0.0,0.0]
[0.0,0.0,拍攝距離,0.0]
[0.0,0.0,0.0,1.0]
再與逆視圖矩陣相乘得到變換所需模型矩陣,并賦予視頻貼地渲染基元的模型矩陣參數(modelMatrix),完成視頻監控畫面變換到世界坐標系。最后將模擬的視頻監控設備的視圖矩陣與逆視圖投影矩陣相乘,得到立方體模型視圖,同理得到逆立方體模型視圖。將這兩個模型視圖賦予分類基元類的一致性映射參數便可以最終實現視頻監控圖像與實景三維場景的融合,融合效果如圖5所示。
融合效果可以通過以下3點判斷:第一,路口斑馬線在投影后的角度符合實際的平行關系,并且位置與實際位置完全重合;第二,建筑立面牌匾寬度和文字完全重合;第三,人行道邊緣完全銜接。
視頻貼地的基元可以動態渲染,只需要在基元的更新方法(update)中,傳入相機參數,判斷視錐體是否發生變化并相應重新計算模型視圖即可,更新方法(update)會在每一幀調用。如將朝向參數從155.4調整為152,視頻向逆時針旋轉,牌匾錯開的效果如圖6所示。
綜上所述,視頻監控圖像投影融合技術路線總結如圖7所示。
圖5視頻監控與實景三維場景的融合效果
圖6視頻貼地的基元動態渲染效果
圖7視頻監控圖像投影融合技術路線
4、視頻監控圖像拼接
多路視頻監控圖像的拼接就是將多路視頻同時融合到實景三維場景中,依據擇優原則,裁切掉重疊部分,形成連續的監控圖像。CesiumJS的材質類(Material)提供了透明通道參數(alphaImage),該參數接收灰度(黑-灰-白)圖片,然后與視頻監控圖像進行掩碼計算[13],便可實現視頻監控圖像的裁切。本地的灰度圖片在創建和編輯的操作方面都不利于Web平臺的維護,因此,需要在瀏覽器的畫布標簽(canvas)中動態繪制灰度圖。canvas標簽提供了完整的繪制方法,lineTo()方法繪制閉合的可見范圍,fill()方法填充顏色,然后將畫布轉為base64編碼格式的圖片,與相機參數共同存儲即可,如圖8、圖9所示。
圖8視頻圖像裁切的灰度圖
圖9多路視頻融合效果
圖10為圍繞十字路口的五路視頻監控圖像拼接成一幅連續畫面的效果,箭頭所指為監控圖像邊界,圈中可以看到一輛公交車從北向南行駛,連續穿越多個分視頻畫面。實驗中視頻監控設備的安裝參數存在誤差導致畫面中物體穿越存在偏移,也證明相機參數對貼地的效果存在關鍵性的影響。
圖10圖像拼接連續畫面的效果
5、結束語
視頻監控目前已經成為國家公共安全、國防、防災應急、消防、旅游工作中應用最為普遍的技術手段。天網工程、雪亮工程、綜合防治工程、磐石行動、數字城管工程等重要工程都圍繞或重點采用這種技術建設。
在多年的用戶體驗積累[9]中可以總結出,用戶需求在視頻監控的空間屬性方面做出進一步的應用效果提升。尤其是在無人機視頻圖傳的應用不斷深入的背景下,視頻與實景三維數據動態融合的應用前景將更加寬廣。
各部門具體應用可大致總結如下:在公安行業,可應用于人員和車輛的連續跟蹤,重點區域合屏監控等;在城管行業,可應用于早、夜市整條街的同時監控;利用無人機進行火災、洪水、地震等自然災害的實時監控和救援搜索;重要機關單位、廠礦、商場、監獄的室內外安保連續監控;在旅游行業,利用固定視頻監控和無人機視頻圖傳應用于景區巡查與搜救、景區宣傳等。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。