流媒体
一,什么是流媒体
我们这里采用录播第三种技术方案:
1、 播放器通过 http协议从http服务器上下载视频文件进行播放
问题:必须等到视频下载完才可以播放,不支持快进到某个时间点进行播放
2、 播放器通过rtmp协议连接媒体服务器以实时流方式播放视频
使用rtmp协议需要架设媒体服务器,造价高,对于直播多采用此方案。
3、 播放器使用HLS协议连接http服务器(Nginx、Apache tomcat等)实现近实时流方式播放视频
HLS协议:将一个视频通过一个解码器(ffmeg)转换为一个编码格式为m3u8文件(装很多ts文件的索引信息)和若干个ts文件列表,用户只用下载M3u8文件,然后可以实现视频播放和跳转(快进快退)。
MP4,rmvb,avi是文件格式
H264是视频的编码格式。
视频上传到专门的服务器上然后下载,流媒体就是将视频文件分成许多小块儿,将这些小块儿作为数据包通过网络发送出去,实现一边传输视频 数据 包一边观看视频。
二,ffmeg转码方式
(一)使用ffmeg生成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
(二)再将MP4生成M3u8
ffmpeg -i lucene.mp4 -hls_time 10 -hls_list_size 0 -hls_segment_filename ./hls/lucene_%05d.ts ./hls/lucene.m3u8
生成了一个M3U8文件和ts文件类表。
三。视频播放器
技术选型:(选H5)
H5播放器:基于h5自带video标签进行构建,优点是大部分浏览器支持H5,不用再安装第三方的flash播放器,并且随着前端技术的发展,h5技术会越来越成熟。
本项目采用H5播放器,使用Video.js开源播放器。
Video.js是一款基于HTML5世界的网络视频播放器。它支持HTML5和Flash视频,它支持在台式机和移动设备上播放视频。这个项目于2010年中开始,目前已在40万网站使用。
官方地址:http://videojs.com/
2搭建媒体服务器
nginx的config的代理转发:
www.XXX/video(其中的proxy代理路径)的,根据proxy的路径找到upstream(其中的127.0.0.1的90端口),再根据90端口找到本地的保存视频的文件夹。
四,项目中的运用
(一)总体框架
老师通过视频管理系统前端上传视频到媒资管理系统,媒资管理系统将媒资描述性信息保存在mongoDB中(比如文件名称,文件路径),同时上传到指定的视频服务器。
课程管理服务需要从媒资管理系统中检索到媒资信息,并完成媒资信息和课程计划的绑定,然后存在mysql中,
学生访问学习中心前端,点击课程计划三级目录中视频播放,请求学习服务,学习服务查询媒资信息,通过代理转发播放视频。
(二)断点续传
原理:将文件拆成若干个小文件,分块上传,然后全部分块上传完毕后,进行文件的合并。
(三)视频处理
1、监听MQ,接收视频处理消息。
2、进行视频处理。
3、向数据库写入视频处理结果。
视频处理进程属于媒资管理系统的一部分,考虑提高系统的扩展性,将视频处理单独定义视频处理工程。
通过processbuilder调用执行外部命令。
MP4utils将avi转换MP4
hlsvideoutils生成M3u8和ts文件列表。
将相对应的信息保存在mongodb中
1 @Component 2 public class MediaProcessConsumer { 3 4 @Autowired 5 private MediaFileRepository mediaFileRepository; 6 //xc-service-manage-media.ffmpeg-path 7 @Value("${xc-service-manage-media.ffmpeg-path}") 8 private String ffmpegPath; 9 10 @Value("${xc-service-manage-media.video-location}") 11 private String videoLocation; 12 13 14 @RabbitListener(queues = {"${xc-service-manage-media.mq.queue-media-video-processor}"}) 15 public void receiveMediaMessage(String message){ 16 // message : {"mediaId","4848rdgdfg"} 17 18 //转换消息 19 Map map = JSON.parseObject(message, Map.class); 20 String mediaId = (String) map.get("mediaId"); 21 22 //查询mediafile对象信息 23 Optional<MediaFile> mediaFileOptional = mediaFileRepository.findById(mediaId); 24 if (!mediaFileOptional.isPresent()){ 25 return; 26 } 27 MediaFile mediaFile = mediaFileOptional.get(); 28 29 //当前操作的文件类型必须是avi 30 String fileType = mediaFile.getFileType(); 31 if (fileType == null || !"avi".equals(fileType)){ 32 mediaFile.setProcessStatus("303004"); //无需处理 33 mediaFileRepository.save(mediaFile); 34 return; 35 }else{ 36 mediaFile.setProcessStatus("303001"); 37 mediaFileRepository.save(mediaFile); 38 } 39 40 //将avi转成MP4 41 /** 42 * String ffmpeg_path, 43 * String video_path, :被转换的文件位置 44 * String mp4_name,: 生成的mp4文件名称 45 * String mp4folder_path: MP4存储的路径 46 */ 47 String video_path =videoLocation+mediaFile.getFilePath()+mediaFile.getFileName(); 48 String mp4_name = mediaFile.getFileId()+".mp4"; 49 String mp4folder_path = videoLocation+mediaFile.getFilePath(); 50 51 Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpegPath,video_path,mp4_name,mp4folder_path); 52 53 String result = mp4VideoUtil.generateMp4(); 54 55 if (result == null || !"success".equals(result)){ 56 //转换失败 57 mediaFile.setProcessStatus("303003"); 58 MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8(); 59 mediaFileProcess_m3u8.setErrormsg(result); 60 mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8); 61 62 mediaFileRepository.save(mediaFile); 63 return; 64 } 65 66 67 //将mp4转成ts文件列表 & .m3u8文件 68 /** 69 * String ffmpeg_path, 70 * String video_path,:被转换的mp4文件的路径 71 * String m3u8_name,: m3u8文件名称 72 * String m3u8folder_path: 保存m3u8文件与ts文件列表的文件夹路径 73 */ 74 String MP4_video_path =videoLocation+mediaFile.getFilePath()+mp4_name; 75 String m3u8_name = mediaFile.getFileId()+".m3u8"; 76 String m3u8folder_path = videoLocation+mediaFile.getFilePath()+"hls/"; 77 78 HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpegPath,MP4_video_path,m3u8_name,m3u8folder_path); 79 80 String m3u8Result = hlsVideoUtil.generateM3u8(); 81 if (m3u8Result == null || !"success".equals(m3u8Result)){ 82 //转换失败 83 mediaFile.setProcessStatus("303003"); 84 MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8(); 85 mediaFileProcess_m3u8.setErrormsg(result); 86 mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8); 87 88 mediaFileRepository.save(mediaFile); 89 return; 90 } 91 92 //将信息保存到mongo 93 94 MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8(); 95 mediaFileProcess_m3u8.setTslist(hlsVideoUtil.get_ts_list()); 96 97 mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8); 98 99 mediaFile.setProcessStatus("303002"); 100 101 //保存m3u8文件路径 102 mediaFile.setFileUrl(mediaFile.getFilePath()+"hls/"+m3u8_name); 103 104 mediaFileRepository.save(mediaFile); 105 } 106 }
消费者