HLS的工作方式是:将视频分割成多个ts格式的小文件,通过m3u8格式的索引文件对这些m3u8格式的小文件进行索引。一般每10秒一个ts文件html 多视频播放器代码,播放器连接m3u8文件播放。快进时,可以通过m3u8找到对应的index文件,下载对应的ts文件,实现快进快退,近乎实时地播放视频。
IOS、Android设备和主流浏览器都支持HLS协议。
详细参考:
使用HLS方案,可以同时下载和播放,不能使用rtmp等流媒体协议,也无需搭建专用媒体服务器,节省成本。
本项目的按需方案确定为方案3。
2 FFmpeg的基本使用
录制完视频后,我们使用视频编码软件对视频进行编码。本项目使用 FFmpeg 对视频进行编码。
下载:ffmpeg-20180227-fa0c9d6-win64-static.zip,解压。本教程将ffmpeg解压到C:Java_Softxczxffmpeg-20180227-fa0c9d6-win64-static。
FFmpeg被很多开源项目采用,如QQ视频、暴风视频、VLC等。
下载:FFmpeg #build-windows
在path环境变量中配置C:Java_Softxczxffmpeg-20180227-fa0c9d6-win64-staticbin目录。
简单测试:
将 .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
官方文档(英文):
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,是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_d.ts ./hls/lucene.m3u8
-hls_time 以秒为单位设置每个切片的长度
-hls_list_size n:要保存的分片数,设置为0保存所有分片
-hls_segment_filename : 段文件名,d代表5位数字
生成的效果是:lucene.mp4视频文件每隔10秒生成一个ts文件,最后生成一个m3u8文件,就是ts的索引文件。
使用VLC打开m3u8文件,测试播放效果。 VLC是一个免费开源的跨平台多媒体播放器和框架,可以播放大部分多媒体文件,以及DVD、音频CD、VCD和各种流媒体协议。 ()
3.视频(媒体资产)处理开发环境1.创建媒体资产数据库
导入shcool.sql
2. 创建媒体资产服务项目
基于springboot创建项目
3.上传文件3.简历上传的1个解决方案
通常视频文件比较大,所以从媒体资产系统上传文件的要求必须满足大文件的上传要求。 http协议本身对上传文件的大小没有限制,但是客户的网络环境质量和电脑硬件环境参差不齐。是致命的,所以上传大文件最基本的要求就是从断点处恢复上传。
什么是恢复:
引用百度百科:断点续传是指在下载或上传时手动将下载或上传任务(文件或压缩包)分成若干部分,每部分使用一个线程进行上传或上传。下载时,如果遇到网络故障,可以从已经上传或下载的部分继续上传和下载未完成的部分,无需从头开始上传和下载。断点续传可以节省操作时间,提升用户体验。
上传过程如下:
1、在上传之前将文件分成块
2、一块一块上传,上传中断后重新上传,已经上传的块不重新上传就上传
3、上传每个chunk后的最终合并文件
文件下载也是如此。
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;ichunkSize){
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 fileList = new ArrayList(Arrays.asList(fileArray));
// 从小到大排序
Collections.sort(fileList, new Comparator() {
@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介绍
如何在网页上实现可续传?
常见的场景包括:
1、通过 Flash 上传,如 SWFupload、Uploadify。
2、安装浏览器插件,伪装成PC客户端,很少使用。
3、HTML5
随着html5的普及,本项目使用Html5完成分块上传文件。
本项目使用WebUploader完成大文件上传功能的开发。 WebUploader官网地址:
使用WebUploader上传过程如下:
挂钩方法
webuploader 中有很多可用的钩子方法,下面列出了一些重要的方法:
本项目使用以下钩子方法:
1)发送前文件
在开始分割文件之前调用,可以在上传文件之前做一些准备工作,比如检查文件目录是否创建等
2)发送前
在上传文件段之前调用此方法。您可以请求服务器检查该段是否存在。如果已存在,则不会上传该片段。
3)发送后文件
在所有分块上传完成后触发,可以请求服务器合并分块文件。
注册钩子方法源码:
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile"
}
构建网络上传器
在使用 webUploader 之前,您需要创建一个 webUploader 对象。
指定上传chunk的地址:/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
}
页面效果
学生使用:
直接解压数据xc-ui-pc-teach到webstorm工作目录通过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";
}
}
您可以访问:12000/#/media/upload或:82/#/media/upload/查看页面效果。
5.媒体资产服务器编写
服务器需要实现以下功能:
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 {
}
定义控制器
@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将视频转换成流媒体了。
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 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 处理流程
视频合并成功时
将视频处理成m3u8流媒体并将视频信息写入数据库
具体代码:在合并完成方法中调用
@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 player:缺点是需要在客户端电脑上安装 Adobe Flash Player。优点是flash播放器非常成熟,浏览器对flash的支持非常好。
H5播放器:基于h5自带的video标签,优点是大部分浏览器都支持H5,无需安装第三方flash播放器,而且随着前端技术的发展,h5技术会越来越成熟.
本项目采用H5播放器和Video.js开源播放器。
Video.js 是一个基于 HTML5 世界的网络视频播放器。它支持 HTML5 和 Flash 视频html 多视频播放器代码,并且支持在桌面和移动设备上播放视频。该项目于 2010 年年中启动,目前有 400,000 个网站使用。
官方地址:
4.2 下载video.js
视频.js:
videojs-contrib-hls: #安装
(videojs-contrib-hls是一个播放hls的插件)
使用文档:
本教程使用video.js 6.7.version 3,videojs-contrib-hls 5.14.version 1
将数据中提供的插件分配到xc-ui-pc-video项目目录
4.3 搭建媒体服务器
正常使用 video.js 播放视频是通过网页。用户通过浏览器打开网页来播放视频。网页和视频都是从网络服务器请求的。
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并将数据中的video.html复制到C:/CODE/JAVA/school/xc-ui-pc-video/如图:
以上已经使用nginx代理了C:/CODE/JAVA/school/xc-ui-pc-video/目录。所以可以直接通过http请求访问video.html页面
修改src路径为自己的媒体访问路径
用户打开浏览器并输入 :81/video.html 。您可以打开视频播放页面进行播放。
暂无评论内容