性能比对 - C# 中 WaitForExit 卡死,当 Process.Start 调用 FFMPEG 解码 高清视频 时出现
目的
这篇文章的意义,不在于解决问题本身,因为这次问题发生在一种错误的使用条件之下,但是在这个过程中发现了一些有趣的现象,有一些感悟,进行记录。
插入几句话:运行在客户端的CS架构的软件,比WBE网页应用,有一个很大的不同点:浏览器本身解决了对很多客户端的兼容性问题,尽管网页开发时仍然需要根据浏览器类型的不同来进行兼容区分处理,但是针对的浏览器类型的数量是有限的,数量非常少;但是CS架构类型的客户端软件(窗口程序)可能会运行在各种不同的操作系统上,或者操作系统相同但是系统的版本号不同,或者系统版本号相同但是硬件配置不同,因此相同的代码运行的功能,都可能会有不同的表现。软件开发的工作,不仅仅在于编码完成,而在于能够在五花八门的客户机上以良好的兼容性稳定运行,所以会有很多的时间是在处理 由于设备不同 带来的麻烦问题。
问题
我们经常会有需求使用 ffmpeg 来对 视频 文件进行解码 ,正常情况下 把非透明通道的 mp4 视频解码为 JPG 图片序列帧是非常高效的 ,但是 此处对 mp4 视频进行了 png 解码 ,导致 出现了很多问题 ,出现了缓慢、卡死,并且在不同电脑有不同表现, 过程中有一些有趣的发现。
视频属性
在解码的视频是佳能相机录制的原生高清MP4视频, 视频属性信息如下:
大小:40.0 MB (41,965,096 字节)
总帧数:326
时长:00:00:10
帧宽度:1920
帧高度:1080
数据速率:30167kbps
总比特率:30417kbps
帧速率:29.97 帧/秒
比特率:250kbps
频道:2(立体声)
音频采样频率:48.000kHz>
FFMPEG 版本
使用的 ffmpeg.exe (83.5MB), 版本信息如下:
ffmpeg version 7.1-essentials_build-www.gyan.dev Copyright (c) 2000-2024 the FFmpeg developers
built with gcc 14.2.0 (Rev1, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberband
libavutil 59. 39.100 / 59. 39.100
libavcodec 61. 19.100 / 61. 19.100
libavformat 61. 7.100 / 61. 7.100
libavdevice 61. 3.100 / 61. 3.100
libavfilter 10. 4.100 / 10. 4.100
libswscale 8. 3.100 / 8. 3.100
libswresample 5. 3.100 / 5. 3.100
libpostproc 58. 3.100 / 58. 3.100>
不同电脑配置
在2台不同的电脑上进行测试,现在把这2台电脑分别定义为 “电脑A” , "电脑B",后面将用这2个名称代指。
电脑A:
处理器 Intel(R) Core(TM) i9-14900HX 2.20 GHz
机带 RAM 32.0 GB (31.7 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
笔和触控 没有可用于此显示器的笔或触控输入
版本 Windows 11 家庭中文版
版本号 24H2
操作系统版本 26100.3775
体验 Windows 功能体验包 1000.26100.66.0>
电脑B:
处理器 Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz 1.90 GHz
机带 RAM 16.0 GB (15.9 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
笔和触控 为 10 触摸点提供笔和触控支持
版本 Windows 10 专业版
版本号 22H2
操作系统内部版本 19045.3324
体验 Windows Feature Experience Pack 1000.19041.1000.0>
不同方式比对
将要执行的解码任务命令是:
-i "D:\1.mp4" -vf "format=rgba" -f image2 -pix_fmt rgba "D:\1\frame_%05d.png">
分别从 是否出现黑色窗口 、解码时长 、内存占用 这3个方面,来在 电脑A 测试手动模式、调试模式、和运行模式 三种, 在 电脑B 测试手动模式 和 运行模式 两种 。
详细测试数据汇总到了如下的表格图:
对于调用方式,一共验证了14种 , 分别为手动方式,和测试0~测试12。手动方式是指打开命令行窗口来执行解码命令,其他方式都是通过在C#中来通过代码的方式调用,在代码中,它们的具体写法如下(“点击查看代码”):
点击查看代码
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = AppDomain.CurrentDomain.BaseDirectory + "ffmpeg.exe";
startInfo.Arguments = "-i \"D:\\1.mp4\" -vf \"format=rgba\" -f image2 -pix_fmt rgba \"D:\\1\\frame_%05d.png\"";
//startInfo.Arguments = "-i \"D:\\1.mp4\" -f image2 -qscale:v 1 \"D:\\1\\frame_%05d.jpg\"";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
//***********************测试 0
var process = Process.Start(startInfo.FileName, startInfo.Arguments);
Stopwatch stopwatch = new Stopwatch();
////***********************测试1
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
////***********************测试2
////不关心输出内容 避免重定向 只关心结果文件 提高效率
//startInfo.RedirectStandardOutput = false;
////不关心输出内容 避免重定向 只关心结果文件 提高效率
//startInfo.RedirectStandardError = false;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
////***********************测试3
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.OutputDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
////开始异步读取。如果不启用 则以上日志始终不会被读取 当日志量达到指定量(可能4K?)后会卡死
//process.BeginOutputReadLine();
//process.BeginErrorReadLine();
////***********************测试4
//startInfo.RedirectStandardOutput = false;
//startInfo.RedirectStandardError = true;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.BeginErrorReadLine();
////***********************测试5
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = false;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.OutputDataReceived += (s, e) =>
//{
// if (e.Data != null) Console.WriteLine(e.Data);
//};
//process.BeginOutputReadLine();
////***********************测试6
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
////禁用控制台渲染 减少编码开销
//startInfo.StandardOutputEncoding = Encoding.ASCII;
//startInfo.StandardErrorEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.OutputDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
////开始异步读取。如果不启用 则以上日志始终不会被读取 当日志量达到指定量(可能4K?)后会卡死
//process.BeginOutputReadLine();
//process.BeginErrorReadLine();
////***********************测试7
//startInfo.RedirectStandardOutput = false;
//startInfo.RedirectStandardError = true;
////禁用控制台渲染 减少编码开销
//startInfo.StandardErrorEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.BeginErrorReadLine();
////***********************测试8
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = false;
////禁用控制台渲染 减少编码开销
//startInfo.StandardOutputEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
//Stopwatch stopwatch = new Stopwatch();
//process.OutputDataReceived += (s, e) =>
//{
// if (e.Data != null) Console.WriteLine(e.Data);
//};
//process.BeginOutputReadLine();
////***********************测试9
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
////禁用控制台渲染 减少编码开销
//startInfo.StandardOutputEncoding = Encoding.ASCII;
//startInfo.StandardErrorEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
////设置进程优先级
//process.PriorityClass = ProcessPriorityClass.High;
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.OutputDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
////开始异步读取。如果不启用 则以上日志始终不会被读取 当日志量达到指定量(可能4K?)后会卡死
//process.BeginOutputReadLine();
//process.BeginErrorReadLine();
////***********************测试10
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
////禁用控制台渲染 减少编码开销
//startInfo.StandardOutputEncoding = Encoding.ASCII;
//startInfo.StandardErrorEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
////设置进程优先级
//process.PriorityClass = ProcessPriorityClass.High;
//Stopwatch stopwatch = new Stopwatch();
//process.ErrorDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
//process.OutputDataReceived += (s, e) =>
//{
// Console.WriteLine(e.Data);
//};
////开始异步读取。如果不启用 则以上日志始终不会被读取 当日志量达到指定量(可能4K?)后会卡死
//process.BeginOutputReadLine();
//process.BeginErrorReadLine();
////***********************测试11
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = false;
////禁用控制台渲染 减少编码开销
//startInfo.StandardOutputEncoding = Encoding.ASCII;
//var process = Process.Start(startInfo);
////设置进程优先级
//process.PriorityClass = ProcessPriorityClass.High;
//Stopwatch stopwatch = new Stopwatch();
//process.OutputDataReceived += (s, e) =>
//{
// if (e.Data != null) Console.WriteLine(e.Data);
//};
//process.BeginOutputReadLine();
////***********************测试12
//startInfo.RedirectStandardOutput = false;
//startInfo.RedirectStandardError = false;
//var process = Process.Start(startInfo);
////设置进程优先级
//process.PriorityClass = ProcessPriorityClass.High;
//Stopwatch stopwatch = new Stopwatch();
//监控资源
new Action(delegate
{
while (!process.HasExited)
{
Console.WriteLine($"内存使用: {process.WorkingSet64 / 1024}KB");
Thread.Sleep(1000);
}
}).BeginInvoke(null, null);
stopwatch.Start();
var ok = process.WaitForExit(-1);
stopwatch.Stop();
Console.WriteLine("解码用时(毫秒):" + stopwatch.ElapsedMilliseconds);
Console.WriteLine("完成");
Console.ReadLine();
发现规律
根据在错误的条件下发现的一些规律,进而推导出对正确条件下的使用的一些指导意义,总结出来了以下的规律,按照规律的重要性列举如下:
1. 电脑A 是 电脑B 的性能3~4倍
2. 只要启用了正常日志输出(RedirectStandardOutput)或者是错误日志(RedirectStandardError)输出,就一定要异步读取这些消息。
FFmpeg 处理高清视频时会产生大量输出,当缓冲区填满(可能默认约 4KB?)时,进程会阻塞等待父进程读取,如果不及时处理,就会一直卡住,测试1 的卡死就是属于这种情况 ,序列帧 在 正确的时间内被处理完成了 ,但是之后却一直卡住,无法跳出 WaitForExit 。
如果关注的重点与日志内容无关,建议直接关闭 RedirectStandardOutput/RedirectStandardError
3. VS 的调试模式 比 直接运行exe 具有非常大的性能提升, 这可能是因为VS本身的调试模式进行了内存优化,比如对输出管道的优化,也有可能是VS对执行进程赋予了较高的进程优先级。
例如上面的测试数据中,当在exe直接运行模式时, 赋予执行进程高优先级的表现,与VS调试模式下水*接*, 但是, 这在高性能电脑上表现优异, 却在低性能电脑上可能会导致 ffmpeg 在启动时略有卡顿,可能会影响系统稳定,因此这里不建议提升执行进程的优先级。
例如 左下 红框选定的部分是 exe模式下将执行进程设置为高优先级,右上 红框选定的部分是 VS 调试模式下保持默认优先级,他们表现出了相*的水*,真的有很大提升。
正确的条件
以上的测试,实际上是假设在一种错误的条件下来进行测试的, 因为一般mp4视频解码为JPG图像序列就可以了, 我们一般把动图GIF文件或者是包含透明通道的MOV视频来解码为 PNG 图像序列,但是当视频本身已经包含透明通道的情况下,它的解码速度有可能是比较快的,这一部分未进行详细验证,暂不细讨论。
但是对于MP4正常解码为JPG情况,行了一组简单的测试:
-i "D:\1.mp4" -f image2 -qscale:v 1 "D:\1\frame_%05d.jpg">
对上面的解码命令,测试情况如下:
从上图看出,在 测试2 的情况下,整体的表现与第一组解码命令有天壤之别 。
因此, “守规矩”是非常重要的,但是当不守规矩的时候,我们对在破坏中遇到的问题,也保持好奇与学习的心态。
重新封装
根据上面的经验教训,重新封装了对进程的调用,代码如下(“点击查看代码”):
点击查看代码
/// <summary>
/// 隐藏窗口
/// 禁止任何日志输出
/// </summary>
/// <param name="exeFile"></param>
/// <param name="exeArgs"></param>
internal static bool CallExe_Hide_LogNone(string exeFile, string exeArgs, int maxWaitMillsec = 120)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = exeFile;
startInfo.Arguments = exeArgs;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = false;
startInfo.RedirectStandardError = false;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var process = Process.Start(startInfo);
if (maxWaitMillsec <= 0) maxWaitMillsec = 30000;
if (!process.WaitForExit(maxWaitMillsec * 1000))
{
stopwatch.Stop();
Loger.error("外部进程执行结束,用时:" + stopwatch.ElapsedMilliseconds + "毫秒,超时:" + exeFile + " " + exeArgs);
return false;
}
stopwatch.Stop();
if (process.ExitCode != 0)
{
Loger.error("外部进程执行发生了错误,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return false;
}
Loger.info("外部进程执行成功,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return true;
}
catch (Exception ex)
{
Loger.error("外部进程执行发生了异常:" + exeFile + " " + exeArgs + "=>" + ex);
return false;
}
}
/// <summary>
/// 隐藏窗口
/// 输出日志和错误
/// </summary>
/// <param name="exeFile"></param>
/// <param name="exeArgs"></param>
internal static bool CallExe_Hide_LogInfoError(string exeFile, string exeArgs, Action<string> dataInfoReceive, Action<string> dataErrorReceive, int maxWaitMillsec = 120)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = exeFile;
startInfo.Arguments = exeArgs;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
//禁用控制台渲染 减少编码开销
startInfo.StandardErrorEncoding = Encoding.ASCII;
startInfo.StandardOutputEncoding = Encoding.ASCII;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var process = Process.Start(startInfo);
process.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
dataErrorReceive?.Invoke(e.Data);
};
process.OutputDataReceived += (s, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
dataInfoReceive?.Invoke(e.Data);
};
//异步读取
process.BeginErrorReadLine();
process.BeginOutputReadLine();
if (maxWaitMillsec <= 0) maxWaitMillsec = 30000;
if (!process.WaitForExit(maxWaitMillsec * 1000))
{
stopwatch.Stop();
Loger.error("外部进程执行结束,用时:" + stopwatch.ElapsedMilliseconds + "毫秒,超时:" + exeFile + " " + exeArgs);
return false;
}
stopwatch.Stop();
if (process.ExitCode != 0)
{
Loger.error("外部进程执行发生了错误,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return false;
}
Loger.info("外部进程执行成功,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return true;
}
catch (Exception ex)
{
Loger.error("外部进程执行发生了异常:" + exeFile + " " + exeArgs + "=>" + ex);
return false;
}
}
/// <summary>
/// 隐藏窗口
/// 仅输出错误日志
/// </summary>
/// <param name="exeFile"></param>
/// <param name="exeArgs"></param>
internal static bool CallExe_Hide_LogError(string exeFile, string exeArgs, Action<string> dataErrorReceive, int maxWaitMillsec = 120)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = exeFile;
startInfo.Arguments = exeArgs;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = false;
startInfo.RedirectStandardError = true;
//禁用控制台渲染 减少编码开销
startInfo.StandardErrorEncoding = Encoding.ASCII;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var process = Process.Start(startInfo);
process.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
dataErrorReceive?.Invoke(e.Data);
};
//异步读取
process.BeginErrorReadLine();
if (maxWaitMillsec <= 0) maxWaitMillsec = 30000;
if (!process.WaitForExit(maxWaitMillsec * 1000))
{
stopwatch.Stop();
Loger.error("外部进程执行结束,用时:" + stopwatch.ElapsedMilliseconds + "毫秒,超时:" + exeFile + " " + exeArgs);
return false;
}
stopwatch.Stop();
if (process.ExitCode != 0)
{
Loger.error("外部进程执行发生了错误,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return false;
}
Loger.info("外部进程执行成功,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return true;
}
catch (Exception ex)
{
Loger.error("外部进程执行发生了异常:" + exeFile + " " + exeArgs + "=>" + ex);
return false;
}
}
/// <summary>
/// 隐藏窗口
/// 仅输出消息日志
/// </summary>
/// <param name="exeFile"></param>
/// <param name="exeArgs"></param>
internal static bool CallExe_Hide_LogInfo(string exeFile, string exeArgs, Action<string> dataInfoReceive, int maxWaitMillsec = 120)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = exeFile;
startInfo.Arguments = exeArgs;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = false;
//禁用控制台渲染 减少编码开销
startInfo.StandardOutputEncoding = Encoding.ASCII;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var process = Process.Start(startInfo);
process.OutputDataReceived += (s, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
dataInfoReceive?.Invoke(e.Data);
};
//异步读取
process.BeginOutputReadLine();
if (maxWaitMillsec <= 0) maxWaitMillsec = 30000;
if (!process.WaitForExit(maxWaitMillsec * 1000))
{
stopwatch.Stop();
Loger.error("外部进程执行结束,用时:" + stopwatch.ElapsedMilliseconds + "毫秒,超时:" + exeFile + " " + exeArgs);
return false;
}
stopwatch.Stop();
if (process.ExitCode != 0)
{
Loger.error("外部进程执行发生了错误,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return false;
}
Loger.info("外部进程执行成功,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return true;
}
catch (Exception ex)
{
Loger.error("外部进程执行发生了异常:" + exeFile + " " + exeArgs + "=>" + ex);
return false;
}
}
/// <summary>
/// 显示窗口
/// 直接启动进程
/// </summary>
/// <param name="exeFile"></param>
/// <param name="exeArgs"></param>
internal static bool CallExe_Show_Directly(string exeFile, string exeArgs, int maxWaitMillsec = 120)
{
try
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var process = Process.Start(exeFile, exeArgs);
if (maxWaitMillsec <= 0) maxWaitMillsec = 30000;
if (!process.WaitForExit(maxWaitMillsec * 1000))
{
stopwatch.Stop();
Loger.error("外部进程执行结束,用时:" + stopwatch.ElapsedMilliseconds + "毫秒,超时:" + exeFile + " " + exeArgs);
return false;
}
stopwatch.Stop();
if (process.ExitCode != 0)
{
Loger.error("外部进程执行发生了错误,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return false;
}
Loger.info("外部进程执行成功,用时:" + stopwatch.ElapsedMilliseconds + "毫秒:" + exeFile + " " + exeArgs);
return true;
}
catch (Exception ex)
{
Loger.error("外部进程执行发生了异常:" + exeFile + " " + exeArgs + "=>" + ex);
return false;
}
}
性能比对 - C# 中 WaitForExit 卡死,当 Process.Start 调用 FFMPEG 解码 高清视频 时出现的更多相关文章
- Python中if __name__=="__main__" 语句在调用多进程Process过程中的作用分析
2018年2月27日 于创B515 引言 最近准备学习一下如何使用Python中的多进程.在翻看相关书籍.网上资料时发现所有代码都含有if __name__=="__main__" ...
- FFmpeg在JAVA中的使用以及Process.waitFor()引发的阻塞问题
此文已由作者叶海啸授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. FFmpeg是一个开源免费跨平台的视频和音频流方案,可以快速对音视频流进行多方面的处理,本文主要介绍FFmp ...
- 利用.Net中Process类调用netstat命令来判断计算端口的使用情况
利用.Net中Process类调用netstat命令来判断计算端口的使用情况: Process p = new Process();p.StartInfo = new ProcessStartInfo ...
- 解决Eclipse中的卡死现象
解决Eclipse中的卡死现象 取消验证 windows–>perferences–>validation 把 除了manual 下面的全部点掉,build下只留 classpath de ...
- Android中的APK,TASK,PROCESS,USERID之间的关系
开发Android已经有一段时间了,今天接触到底层的东西,所以对于进程,用户的id以及Android中的Task,Apk之间的关系,要做一个研究,下面就是研究结果: apk一般占一个dalvik,一个 ...
- C#代码使用Process类调用SWFTools工具
一.Process类调用SWFTools工具将PDF文档转为swf文档 1 string cmdStr = "D:\\SWFTools\\pdf2swf.exe"; string ...
- eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错? java.lang.ClassNotFoundException: com.branchitech.app.startup.AppStartupContextListener java.lang.ClassN
eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错?java. ...
- WPF中ListBox控件在选择模式(SelectionMode)为Single时仍然出现多个Item被选中的问题
最近在学习WPF过程中使用到了ListBox控件,在使用时遇到下面的奇怪问题: 代码如下: listBox.Items.Add("绘图"); listBox.Items.Add(& ...
- Unity3D中C#和js方法相互调用
通过查找资料,Unity3D中C#和js要相互调用彼此的方法,js文件必须放在"Standard Assets". "Pro Standard Assets" ...
- 在visual studio 2010中调用ffmpeg
转自:http://blog.sina.com.cn/s/blog_4178f4bf01018wqh.html 最近几天一直在折腾ffmpeg,在网上也查了许多资料,费了不少劲,现在在这里和大家分享一 ...
随机推荐
- 区块链特辑——solidity语言基础(五)
Solidity语法基础学习 九.实战项目(一): 学以致用 UP主捐款合约 ·合约要求: ·建立时,需将合约的建立者设定成owner(constructor,msg.sender) ·需记录每个观众 ...
- 浅谈Ubuntu中的软件包
1. 前言 还记得大学第一次接触Ubuntu和Linux的时候,觉得用apt安装想要的软件非常方便.但是有时候出现了问题,各种报错,自己又不懂原理,就会非常抓狂.现在稍微理解一点了,故以较为容易理解的 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 32 期(2025年3.24-3.31)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- 0x04 数学知识
数学知识 数论 质数 试除法判定质数 \(O(\sqrt n)\) bool is_prime(int x) { if (x < 2) return false; for (int i = 2; ...
- 自动驾驶 | 为CarLA添加一辆小米SU7 Part I
自动驾驶 | 为CarLA添加一辆小米SU7 Part I 导言 什么是CarLA? CarLA是一款基于虚幻引擎4(Unreal Engine 4)构建的开源自动驾驶仿真平台,为自动驾驶算法的研发. ...
- JAVA基础之多线程三期--线程安全问题
一.线程安全问题就是指:多个线程并发访问同一个资源而发生安全性的问题, 线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写 操作,一般来说,这个全局变量是 ...
- java基础之数据结构
一.栈:stack,又称堆栈[出口和入口在同一侧],特点:先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素) 例子:子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当 ...
- Git撤销本地commit(未push)
查询commit日志 git log 查询到自己commit的上个版本id(commit_id) 撤销(这里是放弃自己commit的更改,直接回退到上个版本源码) git reset --hard c ...
- 【自用】MySQL数据库基本操作
docker 中下载 mysql docker pull mysql 启动 docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=12 ...
- app自动化:Androiddriver操作api
一.获取操作的api 1.currentActivity():获取当前activity 一般获取到当前activity与预期进行断言 androidDriver.currentActivity(); ...