网站实现视频上传、转码、截图及在线播放功能
FLV就是随着FlashMX的推出发展而来的视频格式,目前被众多新一代视频分享网站所采用,是目前增长最快、最为广泛的视频传播格式。是在sorenson公司的压缩算法的基础上开发出来的。FLV格式不仅可以轻松的导入Flash中,速度极快,并且能其到保护版权的作用,并且可以不通过本地的微软或者REAL播放器播放视频。
说到底需要实现在线播放视频,就需要上传的视频格式转换成FLV格式。因此我们可以总结出实现视频在线播放功能有4大基本步骤:
对于视频上传在这里我就不详细说了,跟我们平时做的文件上传功能差不多。一般都是用第三方开源工具commons-fileupload.jar。具体需要用到的jar如下:
上传页面表单配置必须为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>
播放效果如下:
至此整个视频上传-转码-在线播放功能就基本实现了。