稍大些的站点都是要针对图片/视频/音频/文件做集群化的存储,我这里叙述自己对视频文件的集群化存储解决方案.图片方案较此更加简单,因为不存在视频转换服务器.

图述

视频存储方案Java 视频集中存储方案_数据库

再解释:

1、客户端1发送视频文件1.avi 上传请求,通过负载均衡设备,请求被转交给WebServer集群中的一台处理.

2、WebServer直接将文件流传送至视频转换专用服务器,不做其他处理

3、视频服务器接收源文件并保存在自己的临时目录文件夹下(假设E:/temp/),并将保存视频地址E:/temp/1.avi加入转换视频队列

4、后台线程调用ffmpeg.exe转换视频1.flv 保存当前目录下E:/temp/1.flv

5、视频服务器按照特定算法(机会均等的算法)从数据库获取可用视频存储服务器,将E:/temp/1.flv发送至存储服务器

6、存储服务器保存文件后,返回存储结果(true/false) 无论true/false 视频转换服务器都将视频信息写入数据库

     如果为false 视频文件存储信息表Status为"已转换" 否则为出错信息

7、当客户端2浏览视频文件时 则直接从FivPath指定服务器获取视频信息

Demo结构

由于真正的环境中需要至少三台PC机做测试,恰好手头没货,所以就在一台PC上模拟,建立三个WebSite代表不同的服务器,不同的存储目录代表不同的服务器磁盘,比如: "E:/temp/" 代表视频转换专用服务器存储磁盘  "D:/video/"代表3号存储服务器存储目录

三个WebSite解决方案内容如图:

视频存储方案Java 视频集中存储方案_ffmpeg_02

接下来依次罗列下各WebSite中较核心的代码

WebServer服务器

WebServer中UploadFile.aspx.cs使用WebClient异步上传视频至转换服务器

using System;
using System.Net;

namespace WebServer服务器 {
    public partial class UploadFile : System.Web.UI.Page {
        protected void Page_Load(object sender, EventArgs e) {
        }
        protected void btnOK_Click(object sender, EventArgs e) {
            if(fileuploadVideo.HasFile) {
                //从配置文件获取视频转换文件服务器地址
                string serverURL = Common.GetServerPath() + "?filename=" + fileuploadVideo.FileName;
                WebClient wc = new WebClient();
                //直接将用户上传文件传递给视频转换服务器
                wc.UploadDataCompleted += wc_UploadDataCompleted;
                wc.UploadDataAsync(new Uri(serverURL), fileuploadVideo.FileBytes);
            }
        }

        /// <summary>
        /// 视频转换服务器回调事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void wc_UploadDataCompleted(object sender, UploadDataCompletedEventArgs e) {
            if(e.Result != null) {
                string res = System.Text.Encoding.UTF8.GetString(e.Result);
                Context.Response.Write(res);
            }
        }
    }
}

在看下ShowVideo.aspx.cs接收VideoList.aspx传递过来的视频ID,加载视频操作,前台页面使用插件播放,具体代码见下载文件

using System;
using System.Linq;

namespace WebServer服务器.Videos {
    public partial class ShowVideo : System.Web.UI.Page {
        protected string VideoPath;
        protected string VideoTitle;

        protected void Page_Load(object sender, EventArgs e) {
            int id;
            if(int.TryParse(Request["Id"], out id)) {
                DistributedDemo dd = new DistributedDemo();
                var video = dd.VidoFile.FirstOrDefault(item => item.Id == id);
                if(video != null) {
                    VideoPath = "http://" + video.FivPath;
                    VideoTitle = video.Title;
                }
            }
        }
    }
}

这里也列出效果图:

视频存储方案Java 视频集中存储方案_ffmpeg_03

视频转换服务器

接收文件,存储源文件,文件路径加入转换队列中

using System.IO;
using System.Web;

namespace 视频转换服务器 {
    /// <summary>
    /// UploadVideo 的摘要说明
    /// </summary>
    public class UploadVideo : IHttpHandler {
        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/plain";
            string filename = context.Request["filename"];
            string path = "E:/temp/" + filename;//临时文件夹 这里写死
            string ffmpegPath = context.Request.MapPath("ffmpeg.exe");//获得ffmpeg.exe绝对路径 ConvertVideo类中使用
            ConvertVideo.FFmpegPath = ffmpegPath;
            using(FileStream fs = File.OpenWrite(path)) {
                //保存文件
                context.Request.InputStream.CopyTo(fs);
                //向转换队列插入转换任务
                ConvertVideo.Instance.files.Enqueue(path);
                context.Response.Write("已上传,格式转换中...");
            }
        }

        public bool IsReusable {
            get {
                return false;
            }
        }
    }
}

视频转换+视频信息存储+转存存储服务器

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using BLL;
using Model;

namespace 视频转换服务器 {
    public class ConvertVideo {
        public static readonly ConvertVideo Instance = new ConvertVideo();
        public Queue<string> files = new Queue<string>();
        private string Msg = string.Empty;
        public static string FFmpegPath = string.Empty;//ffmpeg文件
        private string filePath = string.Empty;//视频在存储服务器上的存储磁盘目录
        private ConvertVideo() {
        }

        public void ThreadStart() {
            ThreadPool.QueueUserWorkItem(GetVideo);
        }

        private void GetVideo(Object obj) {
            while(true) {
                if(files.Count == 0) {
                    Thread.Sleep(3000);
                } else {
                    string filename = files.Dequeue();
                    BLL.ServerInfoBLL serverInfoBll = new ServerInfoBLL();
                    //获取可用的视频存储服务器 通过平均算法获得 这里简单的指定为3号存储服务器
                    var si = serverInfoBll.GetModelList(" savestate = 0").Single(s => s.ServerId == 3);
                    TransformVideo(filename, si.ServerIP, si.SavePath, si.SecondRealmNameIP);
                }
            }
        }

        /// <summary>
        /// 转换视频 成功则传递视频存储服务器 否则记录视频信息 并删除源文件
        /// </summary>
        /// <param name="filename">原始文件路径</param>
        /// <param name="saveServer">存储服务器IP</param>
        /// <param name="savePath">存储服务器磁盘目录</param>
        /// <param name="SecondIP">视频在存储服务器上的URL</param>
        private void TransformVideo(string filename, string saveServer, string savePath, string SecondIP) {
            string srcFileName = filename;//原始文件
            string destFile = "E:/temp/" + Path.GetFileNameWithoutExtension(filename) + ".flv";//全部转换为Flv格式
            try {
                //创建并启动一个新进程
                Process p = new Process();
                //设置进程启动信息属性StartInfo,这是ProcessStartInfo类,包括了一些属性和方法:

                p.StartInfo.FileName = FFmpegPath;//程序名
                p.StartInfo.UseShellExecute = false;
                //-y选项的意思是当输出文件存在的时候自动覆盖输出文件,不提示“y/n”这样才能自动化

                p.StartInfo.Arguments = "-i " + srcFileName + " -y -ab 56 -ar 22050 -b 800 -r 29.97 -s 420x340 " + destFile;    //执行参数
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中
                p.ErrorDataReceived += p_ErrorDataReceived;
                p.OutputDataReceived += p_OutputDataReceived;
                p.Start();
                p.BeginErrorReadLine();//开始异步读取
                p.WaitForExit();//阻塞等待进程结束
                p.Close();//关闭进程
                p.Dispose();//释放资源
            } catch(Exception ex) {
                //logger.Error("视频转换过程出错,filename=" + filename, ex);//记录日志
                Msg = "视频转换过程出错";
                return;
            }
            Msg = "转换完成";

            //把flv传到视频存储服务器 存储目录在这里指定
            WebClient wc = new WebClient();
            //根据当前时间获得存储目录 /年/月/日/文件名
            string tempstr = "/" + DateTime.Now.Year + "/" + DateTime.Now.Month + "/" + DateTime.Now.Day + "/" +
                        Path.GetFileName(destFile);
            filePath = savePath + tempstr;//视频在存储服务器保存绝对路径
            string VideoUrlPath = SecondIP + tempstr;//视频对外公开的URL
            byte[] bytes = wc.UploadData(new Uri("http://" + saveServer + "/SaveFile.ashx?filePath=" + filePath),
                           File.ReadAllBytes(destFile));

            string res = System.Text.Encoding.UTF8.GetString(bytes);//视频存储服务器返回存储结果
            if(res == "true") {
                Msg = "视频文件转换且保存成功";
            } else {
                Msg = "存储服务器存储失败..";
                VideoUrlPath = "";//转换失败设置为空
            }
            //视频信息存储数据库 无论成功与否
            Model.VidoFile vf = new VidoFile();
            vf.Status = Msg;
            vf.Title = Path.GetFileNameWithoutExtension(filePath);
            vf.FileExt = Path.GetExtension(filePath);
            vf.FivPath = VideoUrlPath;//存储服务器地址
            BLL.VidoFileBLL bll = new VidoFileBLL();
            bll.AddVido(vf);

            //删除源文件与转换后文件 视需求
            File.Delete(srcFileName);
            File.Delete(destFile);
        }

        static void p_OutputDataReceived(object sender, DataReceivedEventArgs e) {
            //logger.Debug(e.Data);
        }

        static void p_ErrorDataReceived(object sender, DataReceivedEventArgs e) {
            //logger.Debug(e.Data);
        }
    }
}

视频集群之3号服务

using System.IO;
using System.Web;

namespace 视频集群之3号服务 {
    /// <summary>
    /// SaveFile 的摘要说明
    /// </summary>
    public class SaveFile : IHttpHandler {

        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/plain";
            string filePath = context.Request["filePath"];
            string path = Path.GetDirectoryName(filePath);
            if(!Directory.Exists(path)) {
                Directory.CreateDirectory(path);
            }
            using(FileStream fs = File.Open(filePath, FileMode.OpenOrCreate)) {
                context.Request.InputStream.CopyTo(fs);
                context.Response.Write("true");
            }
        }

        public bool IsReusable {
            get {
                return false;
            }
        }
    }
}

注意与补充

1.D盘目录下必须有video文件夹

2.E盘目录下必须有temp文件夹

3.D:/video添加至IIS中 指定端口为9998

 

视频存储方案Java 视频集中存储方案_ffmpeg_04