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.sql2. 创建媒资服务工程基于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 = 102410241; //分块数量 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、依次从合并的文件中读取数据向合并文件写入数据//测试文件合并方法@Testpublic 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:110241024, // 分块大小(默认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}
页面效果学员使用:直接解压资料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"; } }
可以访问 http://127.0.0.1:12000/#/media/upload 或者 http://localhost: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路径
定义Dao使用mybatis-plus@Mapper@Componentpublic interface FileMsgMapper extends BaseMapper<FileMsg> {}
定义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); }}
定义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转mp4HlsVideoUtil.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播放器:缺点是需要在客户机安装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.jsVideo.js: https://github.com/videojs/video.jsvideojs-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把资料中的video.html 复制到 C:/CODE/JAVA/school/xc-ui-pc-video/ 如图:上面已经使用nginx 代理 C:/CODE/JAVA/school/xc-ui-pc-video/ 目录。 所以可以通过http请求直接访问video.html页面把src路径修改为自己的媒资访问路径测试:用户打开浏览器输入 http://localhost:81/video.html 。即可打开视频播放页面,并播放。(图片来源网络,侵删)
0 评论