FLV就是随着FlashMX的推出发展而来的视频格式,目前被众多新一代视频分享网站所采用,是目前增长最快、最为广泛的视频传播格式。是在sorenson公司的压缩算法的基础上开发出来的。FLV格式不仅可以轻松的导入Flash中,速度极快,并且能其到保护版权的作用,并且可以不通过本地的微软或者REAL播放器播放视频。
说到底需要实现在线播放视频,就需要上传的视频格式转换成FLV格式。因此我们可以总结出实现视频在线播放功能有4大基本步骤:
上传页面表单配置必须为multipart/form-data格式,如下例子:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> <!-- enctype 默认是 application/x-www-form-urlencoded --> <form action="FileUpLoad" enctype="multipart/form-data" method="post" > 普通表单:<input type="text" name="usename"> <br/> 上传文件1:<input type="file" name="file1"><br/> 上传文件2: <input type="file" name="file2"><br/> <input type="submit" value="提交"/> </form> </body> </html>
上传处理后台servlet一般如一下代码:
package com.mediaplayer.servlet; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; /** * * @author Administrator * 文件上传 * 具体步骤: * 1)获得磁盘文件条目工厂 DiskFileItemFactory 要导包 * 2) 利用 request 获取 真实路径 ,供临时文件存储,和 最终文件存储 ,这两个存储位置可不同,也可相同 * 3)对 DiskFileItemFactory 对象设置一些 属性 * 4)上层API文件上传处理 ServletFileUpload upload = new ServletFileUpload(factory); * 目的是调用 parseRequest(request)方法 获得 FileItem 集合list , * * 5)在 FileItem 对象中 获取信息, 遍历, 判断 表单提交过来的信息 是否是 普通文本信息 另做处理 * 6) * 第一种. 用第三方 提供的 item.write( new File(path,filename) ); 直接写到磁盘上 * 第二种. 手动处理 * */ public class FileUpLoad extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); //设置编码 //获得磁盘文件条目工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //获取文件需要上传到的路径 String path = request.getRealPath("/upload"); //如果没以下两行设置的话,上传大的 文件 会占用 很多内存, //设置暂时存放的 存储室 , 这个存储室,可以和 最终存储文件 的目录不同 /** * 原理 它是先存到 暂时存储室,然后在真正写到 对应目录的硬盘上, * 按理来说 当上传一个文件时,其实是上传了两份,第一个是以 .tem 格式的 * 然后再将其真正写到 对应目录的硬盘上 */ factory.setRepository(new File(path)); //设置 缓存的大小,当上传文件的容量超过该缓存时,直接放到 暂时存储室 factory.setSizeThreshold(1024*1024) ; //上层API文件上传处理 ServletFileUpload upload = new ServletFileUpload(factory); try { //可以上传多个文件 List<FileItem> list = (List<FileItem>)upload.parseRequest(request); for(FileItem item : list) { //获取表单的属性名字 String name = item.getFieldName(); //如果获取的 表单信息是普通的 文本 信息 if(item.isFormField()) { //获取用户具体输入的字符串 ,名字起得挺好,因为表单提交过来的是 字符串类型的 String value = item.getString() ; request.setAttribute(name, value); } //对传入的非 简单的字符串进行处理 ,比如说二进制的 图片,电影这些 else { /** * 以下三步,主要获取 上传文件的名字 */ //获取路径名 String value = item.getName() ; //索引到最后一个反斜杠 int start = value.lastIndexOf("\\"); //截取 上传文件的 字符串名字,加1是 去掉反斜杠, String filename = value.substring(start+1); request.setAttribute(name, filename); //真正写到磁盘上 //它抛出的异常 用exception 捕捉 //item.write( new File(path,filename) );//第三方提供的 //手动写的 OutputStream out = new FileOutputStream(new File(path,filename)); InputStream in = item.getInputStream() ; int length = 0 ; byte [] buf = new byte[1024] ; System.out.println("获取上传文件的总共的容量:"+item.getSize()); // in.read(buf) 每次读到的数据存放在 buf 数组中 while( (length = in.read(buf) ) != -1) { //在 buf 数组中 取出数据 写到 (输出流)磁盘上 out.write(buf, 0, length); } in.close(); out.close(); } } } catch (FileUploadException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); } request.getRequestDispatcher("filedemo.jsp").forward(request, response); } }
当后台接收到上传的视频文件后我们需要将视频格式进行转换,将其他格式转换成flv就需要一个解码器的支持。比较通用的是多媒体视频处理工具ffmpeg。我们先了解一下ffmpeg能够做的事情,ffmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。ffmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用。
1.能支持的格式
ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
2.不能支持的格式
对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式。
下面我们看看视频转换代码如何实现:
package com.mediaplayer.dao; /** * * 功能说明:视频处理dao * @author ljf email: [email protected] * @date 2014-10-14 上午10:32:31 * @version 2.0.0 * @since JDK1.6 * */ public interface MediaDao { /** * 视频转码 * @param srcFilePath 用于指定要转换格式的文件,要截图的视频源文件 * @param codcFilePath 格式转换后的的文件保存路径 * @param mediaPicPath 截图保存路径 * @return */ public boolean executeCodecs(String srcFilePath, String codcFilePath, String mediaPicPath); /** * 可转换为FLV视频文件 * @param file * @return */ public boolean isConvertFLV(String file); /** * 可转换为AVI视频文件 * @param file * @return */ public boolean isConvertAVI(String file); /** * 删除中间转换视频文件 * @param tempFile */ public void deleteAVIFile(String tempFile); }
package com.mediaplayer.dao; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class MediaDaoImpl implements MediaDao { public boolean isConvertFLV(String file) { boolean result = false; String ext = file.substring(file.lastIndexOf(".") + 1, file.length()).toLowerCase(); // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) if (ext.equals("avi")) { result = true; } else if (ext.equals("mpg")) { result = true; } else if (ext.equals("wmv")) { result = true; } else if (ext.equals("3gp")) { result = true; } else if (ext.equals("mov")) { result = true; } else if (ext.equals("mp4")) { result = true; } else if (ext.equals("asf")) { result = true; } else if (ext.equals("asx")) { result = true; } else if (ext.equals("flv")) { result = true; } return result; } public boolean isConvertAVI(String file) { boolean result = false; String ext = file.substring(file.lastIndexOf(".") + 1, file.length()).toLowerCase(); // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式. if (ext.equals("wmv9")) { result = true; } else if (ext.equals("rm")) { result = true; } else if (ext.equals("rmvb")) { result = true; } return result; } public boolean executeCodecs(String srcFilePath, String codcFilePath, String mediaPicPath) { String basePath = System.getProperty("user.dir"); String mencoderPath = "D:\\bbsp\\work\\MediaPlayer\\src\\main\\resources\\tools\\mencoder.exe"; String ffmpegPath = "D:\\bbsp\\work\\MediaPlayer\\src\\main\\resources\\tools\\ffmpeg.exe"; boolean mark = true; String tempPath = basePath + File.separator + "temp" + File.separator + String.valueOf(System.currentTimeMillis())+ ".avi"; if(isConvertAVI(srcFilePath)){ mark = this.convertAVI(mencoderPath, srcFilePath, tempPath); srcFilePath = tempPath; } if(isConvertFLV(srcFilePath) && mark){ mark = this.convertFLV(ffmpegPath, srcFilePath, codcFilePath); mark = this.cutPic(ffmpegPath, srcFilePath, mediaPicPath); }else{ System.out.println("该视频格式无法转换"); mark = false; } this.deleteAVIFile(tempPath); return mark; } private boolean convertFLV(String ffmpegPath,String srcFilePath, String codcFilePath) { File file = new File(ffmpegPath); File srcFile = new File(srcFilePath); if(file.exists()){ System.out.println("转换工具存在"); } if(srcFile.exists()){ System.out.println("源视频存在"); } // 创建一个List集合来保存转换视频文件为flv格式的命令 List<String> convert = new ArrayList<String>(); convert.add(ffmpegPath); // 添加转换工具路径 convert.add("-i"); // 添加参数"-i",该参数指定要转换的文件 convert.add(srcFilePath); // 添加要转换格式的视频文件的路径 convert.add("-ab"); //设置音频码率 convert.add("128"); convert.add("-ac"); //设置声道数 convert.add("2"); convert.add("-qscale"); convert.add("6"); convert.add("-ar"); //设置声音的采样频率 convert.add("22050"); convert.add("-r"); //设置帧频 convert.add("29.97"); convert.add("-b"); convert.add("5942.13"); convert.add("-s"); convert.add("1280x720"); convert.add("-y"); // 添加参数"-y",该参数指定将覆盖已存在的文件 convert.add(codcFilePath); boolean mark = true; try { Process proc = new ProcessBuilder(convert).redirectErrorStream(true).start(); BufferedReader stdout = new BufferedReader( new InputStreamReader(proc.getInputStream())); String line; while ((line = stdout.readLine()) != null) { System.out.println(line); } } catch (Exception e) { mark = false; System.out.println(e); e.printStackTrace(); } return mark; } private Boolean cutPic(String ffmpegPath, String srcFilePath, String mediaPicPath) { // 创建一个List集合来保存从视频中截取图片的命令 List<String> cutpic = new ArrayList<String>(); cutpic.add(ffmpegPath); cutpic.add("-i"); cutpic.add(srcFilePath); // 同上(指定的文件即可以是转换为flv格式之前的文件,也可以是转换的flv文件) cutpic.add("-y"); cutpic.add("-f"); cutpic.add("image2"); cutpic.add("-ss"); // 添加参数"-ss",该参数指定截取的起始时间 cutpic.add("7"); // 添加起始时间为第17秒 cutpic.add("-t"); // 添加参数"-t",该参数指定持续时间 cutpic.add("0.001"); // 添加持续时间为1毫秒 cutpic.add("-s"); // 添加参数"-s",该参数指定截取的图片大小 cutpic.add("800*280"); // 添加截取的图片大小为350*240 cutpic.add(mediaPicPath); // 添加截取的图片的保存路径 boolean mark = true; ProcessBuilder builder = new ProcessBuilder(); try { builder.command(cutpic); builder.redirectErrorStream(true); // 如果此属性为 true,则任何由通过此对象的 start() 方法启动的后续子进程生成的错误输出都将与标准输出合并, //因此两者均可使用 Process.getInputStream() 方法读取。这使得关联错误消息和相应的输出变得更容易 builder.start(); } catch (Exception e) { mark = false; System.out.println(e); e.printStackTrace(); } return mark; } private boolean convertAVI(String mencoderPath,String srcFilePath, String codcFilePath) { List<String> commend = new ArrayList<String>(); commend.add(mencoderPath); commend.add(srcFilePath); commend.add("-oac"); commend.add("lavc"); commend.add("-lavcopts"); commend.add("acodec=mp3:abitrate=64"); commend.add("-ovc"); commend.add("xvid"); commend.add("-xvidencopts"); commend.add("bitrate=600"); commend.add("-of"); commend.add("avi"); commend.add("-o"); commend.add(codcFilePath); try { ProcessBuilder builder = new ProcessBuilder(); builder.command(commend); builder.redirectErrorStream(true);//后续子进程错误输出与标准输出合并 Process p = builder.start(); p.getInputStream(); //后续进程等待Mencoder进程转换结束后才可进行 p.waitFor(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void deleteAVIFile(String tempFile) { File file = new File(tempFile); if(file.exists()){ file.delete(); } } }
package com.mediaplayer.service; public interface MediaService { /** * 视频转码 * @param srcFilePath 用于指定要转换格式的文件,要截图的视频源文件 * @param codcFilePath 格式转换后的的文件保存路径 * @param mediaPicPath 截图保存路径 * @return */ public boolean executeCodecs(String srcFilePath, String codcFilePath, String mediaPicPath); }
package com.mediaplayer.service; import com.mediaplayer.dao.MediaDao; public class MediaServiceImpl implements MediaService { private MediaDao mediaDao; public MediaDao getMediaDao() { return mediaDao; } public void setMediaDao(MediaDao mediaDao) { this.mediaDao = mediaDao; } public boolean executeCodecs(String srcFilePath, String codcFilePath, String mediaPicPath) { return mediaDao.executeCodecs(srcFilePath, codcFilePath, mediaPicPath); } }
接下来我们再写一个测试类:
package com.mediaplayer.test; import com.mediaplayer.dao.MediaDao; import com.mediaplayer.dao.MediaDaoImpl; import com.mediaplayer.service.MediaServiceImpl; public class TestMediaPlayer { public static void main(String[] args) { //自定义方式产生文件名 String serialName = String.valueOf(System.currentTimeMillis()); String srcFilePath ="D:\\bbsp\\work\\MediaPlayer\\src\\main\\resources\\videos\\Wildlife.wmv"; String codcFilePath = "D:\\bbsp\\work\\MediaPlayer\\src\\main\\resources\\videos\\" + serialName + ".flv"; String mediaPicPath = "D:\\bbsp\\work\\MediaPlayer\\src\\main\\resources\\images\\" + serialName + ".jpg"; MediaDao mediaDao = new MediaDaoImpl(); MediaServiceImpl mediaService = new MediaServiceImpl(); mediaService.setMediaDao(mediaDao); mediaService.executeCodecs(srcFilePath, codcFilePath, mediaPicPath); } }
最后我们可以看到转换出来的flv视频文件与视频截图出来的jpg文件:
截图预览就不多说了,只需要建立一个<img>标签指向需要显示的图片url就可以了:
<img alt="图片预览" src="D:/bbsp/work/MediaPlayer/src/main/resources/images/1413339390664.jpg">
4、实现在线播放
最后需要使用flv播放器进行在线播放flv视频。常见的播放器有同感Flash制作出来的player.sef播放器跟vcastr2.swf播放器。我这里就使用vcastr2.swf作为例子,我们需要在页面中嵌入播放器跟视频地址相关配置信息:
<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> <script type="text/javascript"> var swf_width="95%"; var swf_height="90%"; var files='1413339390664.flv'; var config='0:自动播放|1:连续播放|100:默认音量|0:控制栏位置|2:控制栏显示|0x000033:主体颜色|60:主体透明度|0x66ff00:光晕颜色|0xffffff:图标颜色|0xffffff:文字颜色|:logo文字|:logo地址|:结束swf地址'; var texts='Flv视频在线播放测试'; document.write('<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="'+swf_width+'" height="'+swf_height+'">'); document.write('<param name="movie" value="vcastr2.swf"/>'); document.write('<param name="quality" value="high"/>'); document.write('<param name="menu" value="false"/>'); document.write('<param name=wmode value="opaque"/>'); document.write('<param name="FlashVars" value="vcastr_file='+files+'&vcastr_title='+texts+'&vcastr_config='+config+'">'); document.write('<embed src="vcastr2.swf"/>" wmode="opaque" FlashVars="vcastr_file='+files+'&vcastr_title='+texts+'&vcastr_config='+config+'" menu="false" quality="high" width="'+swf_width+'" height="'+swf_height+'" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />'); document.write('</object>'); </script> <html> <body> </body> </html>
播放效果如下:
至此整个视频上传-转码-在线播放功能就基本实现了。