二、实践与视频解决方案
一、视频解决方案
说明:
公司下户拍摄视频,上传存储一直用的优酷云(视频压缩、解码、播放)当然还支持水印。
现在场景,我们公司内部买服务器,下户拍摄视频上传到我们自己服务内,需要解决的问题,下户拍摄视频很大,需要解决的问题:
1、(下户视频过大)需要压缩处理、
2、(视频格式、播放帧处理)解码格式
3、(提供url)提供接口让内部人员可以播放
解决方案1、亲测
使用官网:ffmpeg 、GitHub:https://github.com/FFmpeg/FFmpeg
在下载页面上,我们可以看到,对于32位和64位版本,分别提供了三种不同的模式:static、shared和dev
- static: 该版本提供了静态版本的FFmpeg工具,将依赖的库生成在了最终的可执行文件中;作为工具而言此版本就可以满足我们的需求;
- share: 该版本的工具包括可执行文件和dll,程序运行过程必须依赖于提供的dll文件;
- dev: 提供了库的头文件和dll的引导库;
这里下载的是static版本,将其下载解压到项目目录下:
C#代码:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ffmpeg { class Program { static string FFmpegPath = @"C:\Users\Bruce\source\repos\ffmpeg\ffmpeg\bin\Debug\ffmpeg-20190820-74e6800-win64-static\bin\ffmpeg.exe"; static void Main(string[] args) { string videoUrl = @"D:\video\Wildlife.wmv"; string targetUrl = @"D:\video\newFile.mp4"; //视频转码 string para = string.Format("-i {0} -b 1024k -acodec copy -f mp4 {1}", videoUrl, targetUrl); RunMyProcess(para); Console.WriteLine("完成!"); Console.ReadKey(); } static void RunMyProcess(string Parameters) { var p = new Process(); p.StartInfo.FileName = FFmpegPath; p.StartInfo.Arguments = Parameters; //是否使用操作系统shell启动 p.StartInfo.UseShellExecute = false; //不显示程序窗口 p.StartInfo.CreateNoWindow = true; p.Start(); Console.WriteLine("\n开始转码...\n"); p.WaitForExit(); p.Close(); p.Dispose();//释放资源 } } }
引入exe文件
但是由于ffmpeg视频转码,转码完成后程序一直卡死且一直未收到exit消息,
原因是ffmpeg是StandardError流,要读取StandardError而不是StandardOutput。
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ffmpeg { class Program { static string FFmpegPath = @"C:\Users\Bruce\source\repos\ffmpeg\ffmpeg\bin\Debug\ffmpeg-20190820-74e6800-win64-static\bin\ffmpeg.exe"; static void Main(string[] args) { string videoUrl = @"D:\video\Wildlife.mp4"; string targetUrl = @"D:\video\newFile2.mp4"; //视频转码 string para = string.Format("-i {0} -r 10 -b:a 32k {1}", videoUrl, targetUrl); RunMyProcess(para); Console.WriteLine("转码结束时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "\n完成!"); Console.ReadKey(); } static void RunMyProcess(string Parameters) { var p = new Process(); p.StartInfo.FileName = FFmpegPath; p.StartInfo.Arguments = Parameters; //是否使用操作系统shell启动 p.StartInfo.UseShellExecute = false; //不显示程序窗口 p.StartInfo.CreateNoWindow = true; p.StartInfo.RedirectStandardError = true;//重定向标准错误输出 p.ErrorDataReceived += new DataReceivedEventHandler(Output);//外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN p.Start(); p.BeginErrorReadLine();//开始异步读取 Console.WriteLine("转码开始时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "\n开始转码...\n"); p.WaitForExit();//阻塞等待进程结束 p.Close();//关闭进程 p.Dispose();//释放资源 } private static void Output(object sendProcess, DataReceivedEventArgs output) { if (!String.IsNullOrEmpty(output.Data)) { //处理方法... Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")+":" +output.Data); } } } }
2、视频大小信息
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ffmpeg { class Program { static string FFmpegPath = @"C:\Users\Bruce\source\repos\ffmpeg\ffmpeg\bin\Debug\ffmpeg-20190820-74e6800-win64-static\bin\ffmpeg.exe"; static void Main(string[] args) { string videoUrl = @"D:\video\Wildlife.MP4"; //视频转码 int time = GetVideoDuration(FFmpegPath, videoUrl); Console.WriteLine("视频时间:" + time + "秒"); Console.ReadKey(); } private static int GetVideoDuration(string ffmpegfile, string sourceFile) { try { using (System.Diagnostics.Process ffmpeg = new System.Diagnostics.Process()) { String duration; // soon will hold our video‘s duration in the form "HH:MM:SS.UU" String result; // temp variable holding a string representation of our video‘s duration StreamReader errorreader; // StringWriter to hold output from ffmpeg // we want to execute the process without opening a shell ffmpeg.StartInfo.UseShellExecute = false; //ffmpeg.StartInfo.ErrorDialog = false; ffmpeg.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; // redirect StandardError so we can parse it // for some reason the output comes through over StandardError ffmpeg.StartInfo.RedirectStandardError = true; // set the file name of our process, including the full path // (as well as quotes, as if you were calling it from the command-line) ffmpeg.StartInfo.FileName = ffmpegfile; // set the command-line arguments of our process, including full paths of any files // (as well as quotes, as if you were passing these arguments on the command-line) ffmpeg.StartInfo.Arguments = "-i " + sourceFile; // start the process ffmpeg.Start(); // now that the process is started, we can redirect output to the StreamReader we defined errorreader = ffmpeg.StandardError; // wait until ffmpeg comes back ffmpeg.WaitForExit(); // read the output from ffmpeg, which for some reason is found in Process.StandardError result = errorreader.ReadToEnd(); // a little convoluded, this string manipulation... // working from the inside out, it: // takes a substring of result, starting from the end of the "Duration: " label contained within, // (execute "ffmpeg.exe -i somevideofile" on the command-line to verify for yourself that it is there) // and going the full length of the timestamp duration = result.Substring(result.IndexOf("Duration: ") + ("Duration: ").Length, ("00:00:00").Length); string[] ss = duration.Split(':'); int h = int.Parse(ss[0]); int m = int.Parse(ss[1]); int s = int.Parse(ss[2]); return h * 3600 + m * 60 + s; } } catch (System.Exception ex) { return 60; } } } }
3、视频进度百分比
代码 转码进度公式=当前时间/总时间*100
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ffmpeg { class Program { static void Main(string[] args) { FFmpeg.Execute(); Console.ReadKey(); } } public class FFmpeg { static int timeCount; public static void Execute() { string ffmpegPath = @"C:\Users\Bruce\source\repos\ffmpeg\ffmpeg\bin\Debug\ffmpeg-20190820-74e6800-win64-static\bin\ffmpeg.exe"; Process p = new Process(); p.StartInfo.FileName = ffmpegPath; p.StartInfo.UseShellExecute = false; string videoUrl = @"D:\video\_Wildlife.mp4"; string targetUrl = @"D:\video\newFile15.mp4"; //视频转码 string para = string.Format("-i {0} -r 10 -b:a 32k {1}", videoUrl, targetUrl); p.StartInfo.Arguments = para; //执行参数 //-i D:/vts-collector/test/swap/7a3c9800-2e2f-42fe-ba39-56b25f972e06.mov -y -ab 56 -ar 22050 -b 800 -r 29.97 -s 420x340 D:/vts-collector/test/swap/7a3c9800-2e2f-42fe-ba39-56b25f972e06_mov_stream.mp4 p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中 p.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived); p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); using (p) { p.Start(); p.BeginErrorReadLine();//开始异步读取 p.WaitForExit();//阻塞等待进程结束 p.Close();//关闭进程 } } private static void p_ErrorDataReceived(object sender, DataReceivedEventArgs e) { var output = e.Data; if (output != null) { if (output.Contains("Duration")) { Int32 indexOfDuration = output.IndexOf("Duration"); Int32 indexOfBegin = output.IndexOf(":", indexOfDuration); Int32 indexOfEnd = output.IndexOf(",", indexOfBegin); var duration = output.Substring(indexOfBegin + 1, indexOfEnd - indexOfBegin - 1); timeCount = ConvertStringtoSecond(duration); } if (output.Contains("time=")) { Int32 indexOfTime = output.IndexOf("time="); Int32 indexOfBegin = output.IndexOf("=", indexOfTime); Int32 indexOfEnd = output.IndexOf("bitrate", indexOfBegin); string timeStr = output.Substring(indexOfBegin + 1, indexOfEnd - indexOfBegin - 1); // var time = Convert.ToDouble(ConvertStringtoSecond(timeStr)); // var process = time / timeCount * 100; //当前毫秒单位 process = Math.Round(process); Console.Write("{0}% ", process); } //Console.WriteLine(e.Data); } } private static int ConvertStringtoSecond(string timeStr) { int totalSecond = 0; try { DateTime dateTime = Convert.ToDateTime(timeStr); totalSecond = dateTime.Hour * 3600000 + dateTime.Minute * 60000 + dateTime.Second * 1000 + dateTime.Millisecond; } catch (System.Exception ex) { Console.Write(ex.Message); } return totalSecond; } private static void p_OutputDataReceived(object sender, DataReceivedEventArgs e) { Console.WriteLine(e.Data); } } }
4、视频转码其他指令
string para = string.Format("-i {0} -b:v 400k -s 1920*1080 {1}", videoUrl, targetUrl); //模糊不清楚,1920*1080 完全行不通 很模糊 string para = string.Format("-i {0} {1}", videoUrl, targetUrl); //3.67G转换后1.06G 简单压缩方式
截图
ffmpeg.exe -i D:\video\Wildlife.MP4 -ss 5 -vframes 1 D:\video\img.jpg //截图
把视频的前30帧转换成一个Animated Gif
ffmpeg.exe -i D:\video\_Wildlife.MP4 -vframes 30 -y -f gif D:\video\.gif
水印
//加水印: ffmpeg -i D:\video\newFile2.mp4 -i D:\video\img.jpg -filter_complex overlay D:\video\__Wildlife.MP4
参数
说明:LogoName为图片名,overlay=100:100意义为overlay=x:y,在(x,y)坐标处開始加入水印。
左上角:overlay=10:10
右上角:overlay=main_w-overlay_w-10:10
左下角:overlay=10:main_h-overlay_h-10
右下角:overlay=main_w-overlay_w-10:main_h-overlay_h-10
//gif图到左下角视频中,经常看视频,类似那些视频广告gif ffmpeg -y -i D:\video\newFile2.mp4 -ignore_loop 0 -i D:\video\timg.gif -filter_complex overlay=0:H-h D:\video\__Wildlife.MP4
4、论坛说明
这里下载的是dev版本,将其下载解压到项目目录下:
不是很方便调用,就采用
FFmpeg.AutoGen
一、项目搭建nuget添加ffmpeg.autogen引用。
点到为止
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架