整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

什么是RTMP 和 RTSP?它们之间有什么区别?

频盛行的时代,流媒体技术越来越重要,在本文中,我们将讨论两种流行的流媒体协议:RTMP 和 RTSP。

什么是流协议?

简单地说,流协议就是在两个通信系统之间传输多媒体文件的一套规则,它定义了视频文件将如何分解为小数据包以及它们在互联网上传输的顺序,RTMP 与 RTSP 是比较常见的流媒体协议。

RTMP

  • 英文全称:Real-Time Messaging Protocol
  • 中文意思:实时消息传递协议

还记得几年前,当您尝试播放视频时,您的计算机会告诉您更新您的 Flash 插件吗?这背后的技术其实就是RTMP。

RTMP由 Macromedia(后来被 Adobe 收购)开发,用于流式传输到 Flash 播放器,随着 Flash 开始被淘汰并且基于 HTTP 的协议成为流式传输到播放设备的新标准,RTMP在流媒体协议中的地位越来越低。但是丝毫不影响RTMP的使用,因为在端到端直播和点播中,它仍然是王者!

【相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

【免费】FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发-学习视频教程-腾讯课堂

C++音视频配套学习资料点击莬费领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

RTMP的优点

1、低延迟

RTMP使用独占的 1935 端口,无需缓冲,可以实现低延迟。

2、适应性强

所有 RTMP 服务器都可以录制直播媒体流,同时还允许观众跳过部分广播并在直播开始后加入直播流。

3、灵活性

RTMP 支持整合文本、视频和音频,支持 MP3 和 AAC 音频流,也支持MP4、FLV 和 F4V 视频。

RTMP的缺点

1、HTML5 不支持

标准HTML5 播放器不支持 RTMP 流。

2、容易受到带宽问题的影响

RTMP 流经常会出现低带宽问题,造成视频中断。

3、HTTP 不兼容

无法通过 HTTP 流式传输 RTMP,必须需要实现一个特殊的服务器,并使用第三方内容交付网络或使用流媒体视频平台。

RTSP

  • 英文全称:Real-Time Streaming Protocol
  • 中文意思:实时流协议

RTSP旨在建立和维护原始数据源(客户端)和流服务器之间的连接,允许控制流媒体服务器内的娱乐和通信系统,允许暂停和播放功能,RTSP是 RealNetworks于 1996 年 与 Netscape 和哥伦比亚大学联合开发的协议。

这种可靠性和控制性的结合使RTSP成为闭路电视 (CCTV) 和类似 监控系统的热门选择。因此,它是许多 IP 摄像机的首选协议。

RTSP的优点

1、轻松自定义流

可以通过结合不同的协议来开发自己的视频流解决方案。

2、分段流式传输

RTSP 流使观看者能够在下载完成之前访问的视频内容,而不必下载完整的视频以流式传输内容。

RTSP的缺点

1、与 HTTP 不兼容

没有简单的解决方案可以在 Web 浏览器中播放 RTSP流,因为 RTSP 旨在通过私有网络流式传输视频,必须借用额外软件。

2、使用率低

由于视频播放器和流媒体服务并未广泛支持 RTSP 流媒体,因为使用率比较低。

C++音视频配套学习资料点击莬费领取→音视频开发(资料文档+视频教程+面试题)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

RTMP与RTSP怎么选择?

IP 摄像机选择RTSP

几乎所有 IP 摄像机都支持 RTSP,这是因为 IP 摄像机早在 RTMP 协议创建之前就已经存在,与 RTSP 和 IP 摄像机结合使用时,IP 摄像机本身充当 RTSP 服务器,这意味着要将摄像机连接到 IP 摄像机服务器并广播视频。

物联网设备选择RTSP

RTSP 通常内置在无人机或物联网软件中,从而可以访问视频源,它的好处之一是低延迟,确保视频中没有延迟,这对于无人机来说至关重要。

流媒体应用程序选择RTMP

比如各种短视频软件、视频软件等都内置了RTMP,RTMP 是为满足现代流媒体需求而设计的。

总结

RTMP和RTSP是最常用的流媒体协议,本文主要介绍了RTMP和RTSP的概念以及优缺点,对于RTMP和RTSP的选择,还是得看场景。希望本文对您认识RTMP和RTSP有所帮助,最后感谢您的阅读,如果觉得文章对您有帮助,别忘了点赞、收藏⭐哦!有任何问题,欢迎在下方评论区与我讨论!!!

频点播

1. 视频点播需求分析

1.1 需求描述

视频点播需求如下:

1、学生可以在windows浏览器上在线观看视频。

2、播放器具有快进、快退、暂停等基本功能。

1.2 视频点播解决方案

1.2.1 流媒体

  • 流媒体

详细参考: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.2.2 点播方案

本项目包括点播和直播两种方式,我们先调研点播的方案,如下:

1、 播放器通过 http协议从http服务器上下载视频文件进行播放

问题:必须等到视频下载完才可以播放,不支持快进到某个时间点进行播放

2、 播放器通过rtmp协议连接媒体服务器以实时流方式播放视频

使用rtmp协议需要架设媒体服务器,造价高,对于直播多采用此方案。

3、 播放器使用HLS协议连接http服务器(Nginx、Apache等)实现近实时流方式播放视频

HLS协议规定:基于Http协议,视频封装格式为ts,视频的编码格式为H264,音频编码格式为MP3、AAC或者AC-3

  • HLS是什么 ?

HLS的工作方式是:将视频拆分成若干ts格式的小文件,通过m3u8格式的索引文件对这些ts小文件建立索引。一般10秒一个ts文件,播放器连接m3u8文件播放,当快进时通过m3u8即可找到对应的索引文件,并去下载对应的ts文件,从而实现快进、快退以近实时 的方式播放视频。

IOS、Android设备、及各大浏览器都支持HLS协议。

详细参考:https://baike.baidu.com/item/HLS/8328931?fr=aladdin

采用HLS方案即可实现边下载边播放,可以不用使用rtmp等流媒体协议,不用构建专用的媒体服务器,节省成本。

本项目点播方案确定为方案3。

2 FFmpeg 的基本使用

我们将视频录制完成后,使用视频编码软件对视频进行编码,本项目 使用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

2.1 生成m3u8/ts文件

使用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/)

3. 视频(媒资)处理

开发环境

1. 创建媒资数据库

导入shcool.sql

2. 创建媒资服务工程

基于springboot创建工程

3. 上传文件

3.1 断点续传解决方案

通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。

什么是断点续传:

引用百度百科:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。

上传流程如下:

1、上传前先把文件分成块

2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传

3、各分块上传完成最后合并文件


文件下载则同理。

3.2 文件分块与合并

为了更好的理解文件分块上传的原理,下边用java代码测试文件的分块与合并。

3.3 文件分块

文件分块的流程如下:

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();
​
    }

3.4 文件合并

文件合并流程:

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();
​
}

4. 前端页面

WebUploader介绍

如何在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前需要创建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// 允许在文件传输时提前把下一个文件准备好
  }
)

before-send-file

文件开始上传前前端请求服务端准备上传工作。

type:"POST",
url:"/api/media/upload/register",
data:{
  // 文件唯一表示
  fileMd5:this.fileMd5,
  fileName: file.name,
  fileSize:file.size,
  mimetype:file.type,
  fileExt:file.ext
}

before-send

上传分块前前端请求服务端校验分块是否存在。

 
type:"POST",
url:"/api/media/upload/checkchunk",
data:{
  // 文件唯一表示
  fileMd5:this.fileMd5,
  // 当前分块下标
  chunk:block.chunk,
  // 当前分块大小
  chunkSize:block.end-block.start
}

after-send-file

在所有分块上传完成后触发,可以请求服务端合并分块文件。

 
type:"POST",
url:"/api/media/upload/mergechunks",
data:{
  fileMd5:this.fileMd5,
  fileName: file.name,
  fileSize:file.size,
  mimetype:file.type,
  fileExt:file.ext
}

页面效果

学员使用:

  1. 直接解压资料xc-ui-pc-teach到webstorm工作目录
  2. 通过nginx访问,在nginx中配置 (解决跨域)
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/ 查看页面效果。

5. 媒资服务端编写

服务端需要实现如下功能:

1、上传前检查上传环境

检查文件是否上传,已上传则直接返回。

检查文件上传路径是否存在,不存在则创建。

2、分块检查

检查分块文件是否上传,已上传则返回true。

未上传则检查上传路径是否存在,不存在则创建。

3、分块上传

将分块文件上传到指定的路径。

4、合并分块

将所有分块文件合并为一个文件。

在数据库记录文件信息。



  • 由于上传过程复杂,开发时按业务流程分别实现。
  1. 配置

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路径
  1. 定义Dao

使用mybatis-plus

@Mapper
@Component
public interface FileMsgMapper extends BaseMapper<FileMsg> {
}
  1. 定义controller
@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);
    }
}
​
  1. 定义service (略)由于代码过多,请参见源码。

现在视频已经通过断点续传的方式,上传到了我们的媒资服务器, 接下来就要使用FFmpeg将视频转换成 流媒体。

6.视频处理技术方案

如何通过程序进行视频处理?

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

可以直接使用。

6.1 处理流程

当视频合并成功后

  1. 把视频处理成m3u8流媒体
  2. 把视频信息写到数据库

具体代码: 在合并完成方法中调用

    @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文件内容是否完整。

4 播放器

4.1 技术选型

视频编码后要使用播放器对其进行解码、播放视频内容。在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/

4.2 下载video.js

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项目目录下

4.3 搭建媒体服务器

正常使用video.js播放视频是通过一个网页,用户通过浏览器打开网页去播放视频,网页和视频都从web服务器请求。

4.3.1 Nginx媒体服务器

根据上边的流程,我们在媒体服务器上安装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;
        }       
    }

4.4 测试video.js

  1. 把资料中的video.html 复制到 C:/CODE/JAVA/school/xc-ui-pc-video/ 如图:

上面已经使用nginx 代理 C:/CODE/JAVA/school/xc-ui-pc-video/ 目录。 所以可以通过http请求直接访问video.html页面

  1. 把src路径修改为自己的媒资访问路径
  • 测试:

用户打开浏览器输入 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]中可以总结出,用户需求在视频监控的空间属性方面做出进一步的应用效果提升。尤其是在无人机视频图传的应用不断深入的背景下,视频与实景三维数据动态融合的应用前景将更加宽广。


各部门具体应用可大致总结如下:在公安行业,可应用于人员和车辆的连续跟踪,重点区域合屏监控等;在城管行业,可应用于早、夜市整条街的同时监控;利用无人机进行火灾、洪水、地震等自然灾害的实时监控和救援搜索;重要机关单位、厂矿、商场、监狱的室内外安保连续监控;在旅游行业,利用固定视频监控和无人机视频图传应用于景区巡查与搜救、景区宣传等。