网站实现视频上传、转码、截图及在线播放功能

        早些天一老同学问到怎么在网站上传自己制作的视频并在线播放呢?当时想了想自己还真没进行过这方面的应用开发。上传并在线播放视频现在应用非常广泛,优酷、土豆等在这方面应用得非常成熟。正好趁这几天不忙整理了一下这方面的知识。      
    在线视频播放网站主流播放格式为 FLV。我们顺便了解一下什么是FLV        
    FLVFLASHVIDEO的简称,FLV流媒体格式是一种新的视频格式,全称为FlashVideo。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等缺点。 目前各在线视频网站均采用此视频格式。如新浪播客、56、土豆、酷6youtube等,无一例外。FLV已经成为当前视频文件的主流格式。

    FLV就是随着FlashMX的推出发展而来的视频格式,目前被众多新一代视频分享网站所采用,是目前增长最快、最为广泛的视频传播格式。是在sorenson公司的压缩算法的基础上开发出来的。FLV格式不仅可以轻松的导入Flash中,速度极快,并且能其到保护版权的作用,并且可以不通过本地的微软或者REAL播放器播放视频。

说到底需要实现在线播放视频,就需要上传的视频格式转换成FLV格式。因此我们可以总结出实现视频在线播放功能有4大基本步骤:

1、上传视频

      对于视频上传在这里我就不详细说了,跟我们平时做的文件上传功能差不多。一般都是用第三方开源工具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);  
    }  
  
}  
2、转换上传视频格式

       当后台接收到上传的视频文件后我们需要将视频格式进行转换,将其他格式转换成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文件:

 
网站实现视频上传、转码、截图及在线播放功能
 
网站实现视频上传、转码、截图及在线播放功能
 

3、截图预览

     截图预览就不多说了,只需要建立一个<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>

 播放效果如下:
网站实现视频上传、转码、截图及在线播放功能
 至此整个视频上传-转码-在线播放功能就基本实现了。

5、 附上测试用的ffmpeg.exe跟vcastr2.swf

你可能感兴趣的:(上传)