目的

这篇文章的意义,不在于解决问题本身,因为这次问题发生在一种错误的使用条件之下,但是在这个过程中发现了一些有趣的现象,有一些感悟,进行记录。

插入几句话:运行在客户端的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 解码 高清视频 时出现的更多相关文章

  1. Python中if __name__=="__main__" 语句在调用多进程Process过程中的作用分析

    2018年2月27日 于创B515 引言 最近准备学习一下如何使用Python中的多进程.在翻看相关书籍.网上资料时发现所有代码都含有if __name__=="__main__" ...

  2. FFmpeg在JAVA中的使用以及Process.waitFor()引发的阻塞问题

    此文已由作者叶海啸授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. FFmpeg是一个开源免费跨平台的视频和音频流方案,可以快速对音视频流进行多方面的处理,本文主要介绍FFmp ...

  3. 利用.Net中Process类调用netstat命令来判断计算端口的使用情况

    利用.Net中Process类调用netstat命令来判断计算端口的使用情况: Process p = new Process();p.StartInfo = new ProcessStartInfo ...

  4. 解决Eclipse中的卡死现象

    解决Eclipse中的卡死现象 取消验证 windows–>perferences–>validation 把 除了manual 下面的全部点掉,build下只留 classpath de ...

  5. Android中的APK,TASK,PROCESS,USERID之间的关系

    开发Android已经有一段时间了,今天接触到底层的东西,所以对于进程,用户的id以及Android中的Task,Apk之间的关系,要做一个研究,下面就是研究结果: apk一般占一个dalvik,一个 ...

  6. C#代码使用Process类调用SWFTools工具

    一.Process类调用SWFTools工具将PDF文档转为swf文档 1 string cmdStr = "D:\\SWFTools\\pdf2swf.exe"; string ...

  7. 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. ...

  8. WPF中ListBox控件在选择模式(SelectionMode)为Single时仍然出现多个Item被选中的问题

    最近在学习WPF过程中使用到了ListBox控件,在使用时遇到下面的奇怪问题: 代码如下: listBox.Items.Add("绘图"); listBox.Items.Add(& ...

  9. Unity3D中C#和js方法相互调用

    通过查找资料,Unity3D中C#和js要相互调用彼此的方法,js文件必须放在"Standard Assets". "Pro Standard Assets" ...

  10. 在visual studio 2010中调用ffmpeg

    转自:http://blog.sina.com.cn/s/blog_4178f4bf01018wqh.html 最近几天一直在折腾ffmpeg,在网上也查了许多资料,费了不少劲,现在在这里和大家分享一 ...

随机推荐

  1. 张高兴的大模型开发实战:(三)使用 LangGraph 为对话添加历史记录

    目录 基础概念 环境搭建与配置 将对话历史存储至内存 将对话历史存储至 PostgreSQL 在构建聊天机器人时,对话历史记录是提升用户体验的核心功能之一,用户希望机器人能够记住之前的对话内容,从而避 ...

  2. 区块链特辑——solidity语言基础(二)

    Solidity语法基础学习 四.函数类型: 函数 Function function FnName [V] [SM] [return (--)] {} ·[V]:Visibility,可见性: ·[ ...

  3. Web前端入门第 27 问:你知道 CSS 被浏览器分为了几大类吗?

    埋头苦写多年的 CSS,从没注意到 CSS 被浏览器分了类,直到偶然的一次翻阅开发者工具,才发现原来 CSS 属性也被浏览器归类收纳了. Chrome 下面是 Chrome 的开发者工具中 CSS 的 ...

  4. java中运行指令浅析

    后续业务可能需要在程序中运行指令, 所以这里简单探究了一下, 分别从win和linux两个平台进行研究, 又以为java是跨平台语言, 可能二者之间的区别应该只是返回内容与输入指令的不同. (还不是在 ...

  5. ThreadPoolExecutor的内部类Worker详细解析

    一.定义 ThreadPoolExecutor 的内部类 Worker 是线程池的核心实现之一,它封装了线程和任务,并负责执行任务.Worker 类继承自 AbstractQueuedSynchron ...

  6. bat脚本之启动MySQL服务

    @echo off :: 获取管理员权限 %1 mshta vbscript:CreateObject("Shell.Application").ShellExecute(&quo ...

  7. AI实战:Text_To_SQL+Prompt+数据库(MySQL)+MCP

    一.Text-to-SQL应用概述 什么是Text-to-SQL?Text-to-SQL也称为NL2SQL,是将自然语言查询转换为可在关系数据库上执行的SQL查询的技术. 其核心目标是准确捕捉并反映用 ...

  8. .NET周刊【3月第5期 2025-03-30】

    国内文章 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp智能体框架开发语音交互 https://www.cnblogs.com/GreenShad ...

  9. cocos3 Shader的CCProgram模板详解

    这段内容描述的是一个 着色器(Shader) 的基本结构模板,可能用于一种自定义的着色器语言或框架(例如基于某种图形渲染引擎或中间表示语言扩展的着色器定义方式).以下是逐部分解析其含义: 1. CCP ...

  10. react-router-dom嵌套路由实践

    想要通过react-router-dom实现类似vue的router-view的嵌套路由效果,在点击导航菜单时切换页面,官方文档是真的没找到相关内容,现做个总结: 在createBrowserRout ...